| |
| |
| |
| |
| |
|
|
|
|
| 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): |
| |
| 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): |
| |
| assert torch.all(scaffold(points)) |
| return points * 2 |
|
|
| |
| |
| 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) |
|
|
| |
| 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 |
| ) |
|
|
| |
| 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): |
| |
| |
| 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 |
|
|