Spaces:
Running
Running
# 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 omegaconf import DictConfig, OmegaConf | |
from pytorch3d.implicitron.models.implicit_function.voxel_grid_implicit_function import ( | |
VoxelGridImplicitFunction, | |
) | |
from pytorch3d.implicitron.models.renderer.base import ImplicitronRayBundle | |
from pytorch3d.implicitron.tools.config import expand_args_fields, get_default_args | |
from pytorch3d.renderer import ray_bundle_to_ray_points | |
from tests.common_testing import TestCaseMixin | |
class TestVoxelGridImplicitFunction(TestCaseMixin, unittest.TestCase): | |
def setUp(self) -> None: | |
torch.manual_seed(42) | |
expand_args_fields(VoxelGridImplicitFunction) | |
def _get_simple_implicit_function(self, scaffold_res=16): | |
default_cfg = get_default_args(VoxelGridImplicitFunction) | |
custom_cfg = DictConfig( | |
{ | |
"voxel_grid_density_args": { | |
"voxel_grid_FullResolutionVoxelGrid_args": {"n_features": 7} | |
}, | |
"decoder_density_class_type": "ElementwiseDecoder", | |
"decoder_color_class_type": "MLPDecoder", | |
"decoder_color_MLPDecoder_args": { | |
"network_args": { | |
"n_layers": 2, | |
"output_dim": 3, | |
"hidden_dim": 128, | |
} | |
}, | |
"scaffold_resolution": (scaffold_res, scaffold_res, scaffold_res), | |
} | |
) | |
cfg = OmegaConf.merge(default_cfg, custom_cfg) | |
return VoxelGridImplicitFunction(**cfg) | |
def test_forward(self) -> None: | |
""" | |
Test one forward of VoxelGridImplicitFunction. | |
""" | |
func = self._get_simple_implicit_function() | |
n_grids, n_points = 10, 9 | |
raybundle = ImplicitronRayBundle( | |
origins=torch.randn(n_grids, 2, 3, 3), | |
directions=torch.randn(n_grids, 2, 3, 3), | |
lengths=torch.randn(n_grids, 2, 3, n_points), | |
xys=0, | |
) | |
func(raybundle) | |
def test_scaffold_formation(self): | |
""" | |
Test calculating the scaffold. | |
We define a custom density function and make the implicit function use it | |
After calculating the scaffold we compare the density of our custom | |
density function with densities from the scaffold. | |
""" | |
device = "cuda" if torch.cuda.is_available() else "cpu" | |
func = self._get_simple_implicit_function().to(device) | |
func.scaffold_max_pool_kernel_size = 1 | |
def new_density(points): | |
""" | |
Density function which returns 1 if p>(0.5, 0.5, 0.5) or | |
p < (-0.5, -0.5, -0.5) else 0 | |
""" | |
inshape = points.shape | |
points = points.view(-1, 3) | |
out = [] | |
for p in points: | |
if torch.all(p > 0.5) or torch.all(p < -0.5): | |
out.append(torch.tensor([[1.0]])) | |
else: | |
out.append(torch.tensor([[0.0]])) | |
return torch.cat(out).view(*inshape[:-1], 1).to(device) | |
func._get_density = new_density | |
func._get_scaffold(0) | |
points = torch.tensor( | |
[ | |
[0, 0, 0], | |
[1, 1, 1], | |
[1, 0, 0], | |
[0.1, 0, 0], | |
[10, 1, -1], | |
[-0.8, -0.7, -0.9], | |
] | |
).to(device) | |
expected = new_density(points).float().to(device) | |
assert torch.allclose(func.voxel_grid_scaffold(points), expected), ( | |
func.voxel_grid_scaffold(points), | |
expected, | |
) | |
def test_scaffold_filtering(self, n_test_points=100): | |
""" | |
Test that filtering points with scaffold works. | |
We define a scaffold and make the implicit function use it. We also | |
define new density and color functions which check that all passed | |
points are not in empty space (with scaffold function). In the end | |
we compare the result from the implicit function with one calculated | |
simple python, this checks that the points were merged correectly. | |
""" | |
device = "cuda" | |
func = self._get_simple_implicit_function().to(device) | |
def scaffold(points): | |
"""' | |
Function to deterministically and randomly enough assign a point | |
to empty or occupied space. | |
Return 1 if second digit of sum after 0 is odd else 0 | |
""" | |
return ( | |
((points.sum(dim=-1, keepdim=True) * 10**2 % 10).long() % 2) == 1 | |
).float() | |
def new_density(points): | |
# check if all passed points should be passed here | |
assert torch.all(scaffold(points)), (scaffold(points), points.shape) | |
return points.sum(dim=-1, keepdim=True) | |
def new_color(points, camera, directions, non_empty_points, num_points_per_ray): | |
# check if all passed points should be passed here | |
assert torch.all(scaffold(points)) # , (scaffold(points), points) | |
return points * 2 | |
# check both computation paths that they contain only points | |
# which are not in empty space | |
func._get_density = new_density | |
func._get_color = new_color | |
func.voxel_grid_scaffold.forward = scaffold | |
func._scaffold_ready = True | |
bundle = ImplicitronRayBundle( | |
origins=torch.rand((n_test_points, 2, 1, 3), device=device), | |
directions=torch.rand((n_test_points, 2, 1, 3), device=device), | |
lengths=torch.rand((n_test_points, 2, 1, 4), device=device), | |
xys=None, | |
) | |
points = ray_bundle_to_ray_points(bundle) | |
result_density, result_color, _ = func(bundle) | |
# construct the wanted result 'by hand' | |
flat_points = points.view(-1, 3) | |
expected_result_density, expected_result_color = [], [] | |
for point in flat_points: | |
if scaffold(point) == 1: | |
expected_result_density.append(point.sum(dim=-1, keepdim=True)) | |
expected_result_color.append(point * 2) | |
else: | |
expected_result_density.append(point.new_zeros((1,))) | |
expected_result_color.append(point.new_zeros((3,))) | |
expected_result_density = torch.stack(expected_result_density, dim=0).view( | |
*points.shape[:-1], 1 | |
) | |
expected_result_color = torch.stack(expected_result_color, dim=0).view( | |
*points.shape[:-1], 3 | |
) | |
# check that thre result is expected | |
assert torch.allclose(result_density, expected_result_density), ( | |
result_density, | |
expected_result_density, | |
) | |
assert torch.allclose(result_color, expected_result_color), ( | |
result_color, | |
expected_result_color, | |
) | |
def test_cropping(self, scaffold_res=9): | |
""" | |
Tests whether implicit function finds the bounding box of the object and sends | |
correct min and max points to voxel grids for rescaling. | |
""" | |
device = "cuda" if torch.cuda.is_available() else "cpu" | |
func = self._get_simple_implicit_function(scaffold_res=scaffold_res).to(device) | |
assert scaffold_res >= 8 | |
div = (scaffold_res - 1) / 2 | |
true_min_point = torch.tensor( | |
[-3 / div, 0 / div, -3 / div], | |
device=device, | |
) | |
true_max_point = torch.tensor( | |
[1 / div, 2 / div, 3 / div], | |
device=device, | |
) | |
def new_scaffold(points): | |
# 1 if between true_min and true_max point else 0 | |
# return points.new_ones((*points.shape[:-1], 1)) | |
return ( | |
torch.logical_and(true_min_point <= points, points <= true_max_point) | |
.all(dim=-1) | |
.float()[..., None] | |
) | |
called_crop = [] | |
def assert_min_max_points(min_point, max_point): | |
called_crop.append(1) | |
self.assertClose(min_point, true_min_point) | |
self.assertClose(max_point, true_max_point) | |
func.voxel_grid_density.crop_self = assert_min_max_points | |
func.voxel_grid_color.crop_self = assert_min_max_points | |
func.voxel_grid_scaffold.forward = new_scaffold | |
func._scaffold_ready = True | |
func._crop(epoch=0) | |
assert len(called_crop) == 2 | |