|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import unittest |
|
|
|
import numpy as np |
|
|
|
import torch |
|
|
|
from pytorch3d.implicitron.models.renderer.base import ( |
|
approximate_conical_frustum_as_gaussians, |
|
compute_3d_diagonal_covariance_gaussian, |
|
conical_frustum_to_gaussian, |
|
ImplicitronRayBundle, |
|
) |
|
from pytorch3d.implicitron.models.renderer.ray_sampler import AbstractMaskRaySampler |
|
|
|
from tests.common_testing import TestCaseMixin |
|
|
|
|
|
class TestRendererBase(TestCaseMixin, unittest.TestCase): |
|
def test_implicitron_from_bins(self) -> None: |
|
bins = torch.randn(2, 3, 4, 5) |
|
ray_bundle = ImplicitronRayBundle( |
|
origins=None, |
|
directions=None, |
|
lengths=None, |
|
xys=None, |
|
bins=bins, |
|
) |
|
self.assertClose(ray_bundle.lengths, 0.5 * (bins[..., 1:] + bins[..., :-1])) |
|
self.assertClose(ray_bundle.bins, bins) |
|
|
|
def test_implicitron_raise_value_error_bins_is_set_and_try_to_set_lengths( |
|
self, |
|
) -> None: |
|
ray_bundle = ImplicitronRayBundle( |
|
origins=torch.rand(2, 3, 4, 3), |
|
directions=torch.rand(2, 3, 4, 3), |
|
lengths=None, |
|
xys=torch.rand(2, 3, 4, 2), |
|
bins=torch.rand(2, 3, 4, 14), |
|
) |
|
with self.assertRaisesRegex( |
|
ValueError, |
|
"If the bins attribute is not None you cannot set the lengths attribute.", |
|
): |
|
ray_bundle.lengths = torch.empty(2) |
|
|
|
def test_implicitron_raise_value_error_if_bins_dim_equal_1(self) -> None: |
|
with self.assertRaisesRegex( |
|
ValueError, "The last dim of bins must be at least superior or equal to 2." |
|
): |
|
ImplicitronRayBundle( |
|
origins=torch.rand(2, 3, 4, 3), |
|
directions=torch.rand(2, 3, 4, 3), |
|
lengths=None, |
|
xys=torch.rand(2, 3, 4, 2), |
|
bins=torch.rand(2, 3, 4, 1), |
|
) |
|
|
|
def test_implicitron_raise_value_error_if_neither_bins_or_lengths_provided( |
|
self, |
|
) -> None: |
|
with self.assertRaisesRegex( |
|
ValueError, |
|
"Please set either bins or lengths to initialize an ImplicitronRayBundle.", |
|
): |
|
ImplicitronRayBundle( |
|
origins=torch.rand(2, 3, 4, 3), |
|
directions=torch.rand(2, 3, 4, 3), |
|
lengths=None, |
|
xys=torch.rand(2, 3, 4, 2), |
|
bins=None, |
|
) |
|
|
|
def test_conical_frustum_to_gaussian(self) -> None: |
|
origins = torch.zeros(3, 3, 3) |
|
directions = torch.tensor( |
|
[ |
|
[[0, 0, 0], [1, 0, 0], [3, 0, 0]], |
|
[[0, 0.25, 0], [1, 0.25, 0], [3, 0.25, 0]], |
|
[[0, 1, 0], [1, 1, 0], [3, 1, 0]], |
|
] |
|
) |
|
bins = torch.tensor( |
|
[ |
|
[[0.5, 1.5], [0.3, 0.7], [0.3, 0.7]], |
|
[[0.5, 1.5], [0.3, 0.7], [0.3, 0.7]], |
|
[[0.5, 1.5], [0.3, 0.7], [0.3, 0.7]], |
|
] |
|
) |
|
|
|
radii = torch.tensor( |
|
[ |
|
[1.25, 2.25, 2.25], |
|
[1.75, 2.75, 2.75], |
|
[1.75, 2.75, 2.75], |
|
] |
|
) |
|
radii = radii[..., None] / 12**0.5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
expected_mean = torch.tensor( |
|
[ |
|
[ |
|
[[0.0, 0.0, 0.0]], |
|
[[0.5506329, 0.0, 0.0]], |
|
[[1.6518986, 0.0, 0.0]], |
|
], |
|
[ |
|
[[0.0, 0.28846154, 0.0]], |
|
[[0.5506329, 0.13765822, 0.0]], |
|
[[1.6518986, 0.13765822, 0.0]], |
|
], |
|
[ |
|
[[0.0, 1.1538461, 0.0]], |
|
[[0.5506329, 0.5506329, 0.0]], |
|
[[1.6518986, 0.5506329, 0.0]], |
|
], |
|
] |
|
) |
|
expected_diag_cov = torch.tensor( |
|
[ |
|
[ |
|
[[0.04544772, 0.04544772, 0.04544772]], |
|
[[0.01130973, 0.03317059, 0.03317059]], |
|
[[0.10178753, 0.03317059, 0.03317059]], |
|
], |
|
[ |
|
[[0.08907752, 0.00404956, 0.08907752]], |
|
[[0.0142245, 0.04734321, 0.04955113]], |
|
[[0.10212927, 0.04991625, 0.04955113]], |
|
], |
|
[ |
|
[[0.08907752, 0.0647929, 0.08907752]], |
|
[[0.03608529, 0.03608529, 0.04955113]], |
|
[[0.10674264, 0.05590574, 0.04955113]], |
|
], |
|
] |
|
) |
|
|
|
ray = ImplicitronRayBundle( |
|
origins=origins, |
|
directions=directions, |
|
bins=bins, |
|
lengths=None, |
|
pixel_radii_2d=radii, |
|
xys=None, |
|
) |
|
mean, diag_cov = conical_frustum_to_gaussian(ray) |
|
|
|
self.assertClose(mean, expected_mean) |
|
self.assertClose(diag_cov, expected_diag_cov) |
|
|
|
def test_scale_conical_frustum_to_gaussian(self) -> None: |
|
origins = torch.zeros(2, 2, 3) |
|
directions = torch.Tensor( |
|
[ |
|
[[0, 1, 0], [0, 0, 1]], |
|
[[0, 1, 0], [0, 0, 1]], |
|
] |
|
) |
|
bins = torch.Tensor( |
|
[ |
|
[[0.5, 1.5], [0.3, 0.7]], |
|
[[0.5, 1.5], [0.3, 0.7]], |
|
] |
|
) |
|
radii = torch.ones(2, 2, 1) |
|
|
|
ray = ImplicitronRayBundle( |
|
origins=origins, |
|
directions=directions, |
|
bins=bins, |
|
pixel_radii_2d=radii, |
|
lengths=None, |
|
xys=None, |
|
) |
|
|
|
mean, diag_cov = conical_frustum_to_gaussian(ray) |
|
|
|
scaling_factor = 2.5 |
|
ray = ImplicitronRayBundle( |
|
origins=origins, |
|
directions=directions, |
|
bins=bins * scaling_factor, |
|
pixel_radii_2d=radii, |
|
lengths=None, |
|
xys=None, |
|
) |
|
mean_scaled, diag_cov_scaled = conical_frustum_to_gaussian(ray) |
|
np.testing.assert_allclose(mean * scaling_factor, mean_scaled) |
|
np.testing.assert_allclose( |
|
diag_cov * scaling_factor**2, diag_cov_scaled, atol=1e-6 |
|
) |
|
|
|
def test_approximate_conical_frustum_as_gaussian(self) -> None: |
|
"""Ensure that the computation modularity in our function is well done.""" |
|
bins = torch.Tensor([[0.5, 1.5], [0.3, 0.7]]) |
|
radii = torch.Tensor([[1.0], [1.0]]) |
|
t_mean, t_var, r_var = approximate_conical_frustum_as_gaussians(bins, radii) |
|
|
|
self.assertEqual(t_mean.shape, (2, 1)) |
|
self.assertEqual(t_var.shape, (2, 1)) |
|
self.assertEqual(r_var.shape, (2, 1)) |
|
|
|
mu = np.array([[1.0], [0.5]]) |
|
delta = np.array([[0.5], [0.2]]) |
|
|
|
np.testing.assert_allclose( |
|
mu + (2 * mu * delta**2) / (3 * mu**2 + delta**2), t_mean.numpy() |
|
) |
|
np.testing.assert_allclose( |
|
(delta**2) / 3 |
|
- (4 / 15) |
|
* ( |
|
(delta**4 * (12 * mu**2 - delta**2)) |
|
/ (3 * mu**2 + delta**2) ** 2 |
|
), |
|
t_var.numpy(), |
|
) |
|
np.testing.assert_allclose( |
|
radii**2 |
|
* ( |
|
(mu**2) / 4 |
|
+ (5 / 12) * delta**2 |
|
- 4 / 15 * (delta**4) / (3 * mu**2 + delta**2) |
|
), |
|
r_var.numpy(), |
|
) |
|
|
|
def test_compute_3d_diagonal_covariance_gaussian(self) -> None: |
|
ray_directions = torch.Tensor([[0, 0, 1]]) |
|
t_var = torch.Tensor([0.5, 0.5, 1]) |
|
r_var = torch.Tensor([0.6, 0.3, 0.4]) |
|
expected_diag_cov = np.array( |
|
[ |
|
[ |
|
|
|
[0.0 + 0.6, 0.0 + 0.6, 0.5 + 0.0], |
|
[0.0 + 0.3, 0.0 + 0.3, 0.5 + 0.0], |
|
[0.0 + 0.4, 0.0 + 0.4, 1.0 + 0.0], |
|
] |
|
] |
|
) |
|
diag_cov = compute_3d_diagonal_covariance_gaussian(ray_directions, t_var, r_var) |
|
np.testing.assert_allclose(diag_cov.numpy(), expected_diag_cov) |
|
|
|
def test_conical_frustum_to_gaussian_raise_valueerror(self) -> None: |
|
lengths = torch.linspace(0, 1, steps=6) |
|
directions = torch.tensor([0, 0, 1]) |
|
origins = torch.tensor([1, 1, 1]) |
|
ray = ImplicitronRayBundle( |
|
origins=origins, directions=directions, lengths=lengths, xys=None |
|
) |
|
|
|
expected_error_message = ( |
|
"RayBundle pixel_radii_2d or bins have not been provided." |
|
" Look at pytorch3d.renderer.implicit.renderer.ray_sampler::" |
|
"AbstractMaskRaySampler to see how to compute them. Have you forgot to set" |
|
"`cast_ray_bundle_as_cone` to True?" |
|
) |
|
|
|
with self.assertRaisesRegex(ValueError, expected_error_message): |
|
_ = conical_frustum_to_gaussian(ray) |
|
|
|
|
|
class FakeRaySampler(AbstractMaskRaySampler): |
|
def _get_min_max_depth_bounds(self, *args): |
|
return None |
|
|
|
message_assertion = ( |
|
"If cast_ray_bundle_as_cone has been removed please update the doc" |
|
"conical_frustum_to_gaussian" |
|
) |
|
self.assertIsNotNone( |
|
getattr(FakeRaySampler(), "cast_ray_bundle_as_cone", None), |
|
message_assertion, |
|
) |
|
|