From 1bd4cc751534527642195966f30c53abc9c1cc33 Mon Sep 17 00:00:00 2001 From: Dave Greenwood Date: Sun, 16 Feb 2020 10:55:00 +0000 Subject: [PATCH 01/10] background_color type hint BlendParams background_color is immutable , type hint as a sequence allows setting new values in constructor. --- pytorch3d/renderer/blending.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytorch3d/renderer/blending.py b/pytorch3d/renderer/blending.py index 1816e767a..0865547a6 100644 --- a/pytorch3d/renderer/blending.py +++ b/pytorch3d/renderer/blending.py @@ -3,7 +3,7 @@ import numpy as np -from typing import NamedTuple +from typing import NamedTuple, Sequence import torch # Example functions for blending the top K colors per pixel using the outputs @@ -15,7 +15,7 @@ class BlendParams(NamedTuple): sigma: float = 1e-4 gamma: float = 1e-4 - background_color = (1.0, 1.0, 1.0) + background_color: Sequence = (1.0, 1.0, 1.0) def hard_rgb_blend(colors, fragments) -> torch.Tensor: From b05a2a4130d860285d1c99a7a1afcee94e75f944 Mon Sep 17 00:00:00 2001 From: Dave Greenwood Date: Sun, 16 Feb 2020 13:49:20 +0000 Subject: [PATCH 02/10] lookout_from_eye_view_transform return R, t from a camera eye point and look at target. --- pytorch3d/renderer/cameras.py | 36 ++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/pytorch3d/renderer/cameras.py b/pytorch3d/renderer/cameras.py index 4e13ad555..92381d6fe 100644 --- a/pytorch3d/renderer/cameras.py +++ b/pytorch3d/renderer/cameras.py @@ -3,7 +3,7 @@ import math import numpy as np -from typing import Tuple +from typing import Tuple, Sequence import torch import torch.nn.functional as F @@ -1002,6 +1002,40 @@ def look_at_rotation( return R.transpose(1, 2) +def look_at_from_eye_view_transform( + eye: Sequence, + at=((0, 0, 0),), # (1, 3) + up=((0, 1, 0),), # (1, 3) + device="cpu", +) -> Tuple[torch.Tensor, torch.Tensor]: + """ + This function returns a rotation and translation matrix + to apply the 'Look At' transformation from world -> view coordinates [0]. + + Args: + eye: the position of the camera(s) in world coordinates. + up: the direction of the x axis in the world coordinate system. + at: the position of the object(s) in world coordinates. + eye up and at can be of shape (1, 3) or (N, 3). + + Returns: + 2-element tuple containing + + - **R**: the rotation to apply to the points to align with the camera. + - **T**: the translation to apply to the points to align with the camera. + + References: + [0] https://www.scratchapixel.com + """ + broadcasted_args = convert_to_tensors_and_broadcast( + eye, at, up, device=device + ) + eye, at, up = broadcasted_args + R = look_at_rotation(eye, at, up, device=device) + T = -torch.bmm(R.transpose(1, 2), eye[:, :, None])[:, :, 0] + return R, T + + def look_at_view_transform( dist, elev, From 5b519ba3b9ad58f6ffdce17f3d23521261e77fcf Mon Sep 17 00:00:00 2001 From: davegreenwood Date: Wed, 19 Feb 2020 16:02:03 +0000 Subject: [PATCH 03/10] added test for using eyepoint in look_at_view_transform --- pytorch3d/renderer/cameras.py | 59 +++++++++++------------------------ tests/test_cameras.py | 17 ++++++++++ 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/pytorch3d/renderer/cameras.py b/pytorch3d/renderer/cameras.py index 92381d6fe..92a891238 100644 --- a/pytorch3d/renderer/cameras.py +++ b/pytorch3d/renderer/cameras.py @@ -1002,45 +1002,12 @@ def look_at_rotation( return R.transpose(1, 2) -def look_at_from_eye_view_transform( - eye: Sequence, - at=((0, 0, 0),), # (1, 3) - up=((0, 1, 0),), # (1, 3) - device="cpu", -) -> Tuple[torch.Tensor, torch.Tensor]: - """ - This function returns a rotation and translation matrix - to apply the 'Look At' transformation from world -> view coordinates [0]. - - Args: - eye: the position of the camera(s) in world coordinates. - up: the direction of the x axis in the world coordinate system. - at: the position of the object(s) in world coordinates. - eye up and at can be of shape (1, 3) or (N, 3). - - Returns: - 2-element tuple containing - - - **R**: the rotation to apply to the points to align with the camera. - - **T**: the translation to apply to the points to align with the camera. - - References: - [0] https://www.scratchapixel.com - """ - broadcasted_args = convert_to_tensors_and_broadcast( - eye, at, up, device=device - ) - eye, at, up = broadcasted_args - R = look_at_rotation(eye, at, up, device=device) - T = -torch.bmm(R.transpose(1, 2), eye[:, :, None])[:, :, 0] - return R, T - - def look_at_view_transform( dist, elev, azim, degrees: bool = True, + eye: Sequence = None, at=((0, 0, 0),), # (1, 3) up=((0, 1, 0),), # (1, 3) device="cpu", @@ -1059,10 +1026,12 @@ def look_at_view_transform( reference vector at (1, 0, 0) on the reference plane. dist, elem and azim can be of shape (1), (N). degrees: boolean flag to indicate if the elevation and azimuth - angles are specified in degrees or raidans. + angles are specified in degrees or radians. + eye: the position of the camera(s) in world coordinates. If eye is not + None, it will overide the camera position derived from dist, elev, azim. up: the direction of the x axis in the world coordinate system. at: the position of the object(s) in world coordinates. - up and at can be of shape (1, 3) or (N, 3). + eye, up and at can be of shape (1, 3) or (N, 3). Returns: 2-element tuple containing @@ -1073,11 +1042,19 @@ def look_at_view_transform( References: [0] https://www.scratchapixel.com """ - broadcasted_args = convert_to_tensors_and_broadcast( - dist, elev, azim, at, up, device=device - ) - dist, elev, azim, at, up = broadcasted_args - C = camera_position_from_spherical_angles(dist, elev, azim, device=device) + + if eye is not None: + broadcasted_args = convert_to_tensors_and_broadcast( + eye, at, up, device=device) + eye, at, up = broadcasted_args + C = eye + else: + broadcasted_args = convert_to_tensors_and_broadcast( + dist, elev, azim, at, up, device=device) + dist, elev, azim, at, up = broadcasted_args + C = camera_position_from_spherical_angles( + dist, elev, azim, degrees=degrees, device=device) + R = look_at_rotation(C, at, up, device=device) T = -torch.bmm(R.transpose(1, 2), C[:, :, None])[:, :, 0] return R, T diff --git a/tests/test_cameras.py b/tests/test_cameras.py index fd7f33be8..2fb65cc08 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -39,6 +39,7 @@ camera_position_from_spherical_angles, get_world_to_view_transform, look_at_rotation, + look_at_view_transform, ) from pytorch3d.transforms import Transform3d from pytorch3d.transforms.so3 import so3_exponential_map @@ -117,6 +118,18 @@ def setUp(self) -> None: torch.manual_seed(42) np.random.seed(42) + def test_look_at_view_transform_from_eye_point_tuple(self): + dist = math.sqrt(2) + elev = math.pi / 4 + azim = 0.0 + eye = ((0.0, 1.0, -1.0), ) + R, t = look_at_view_transform(dist, elev, azim, degrees=False) + # using other values for dist, elev, azim + R_eye, t_eye = look_at_view_transform( + dist=3, elev=2, azim=1, eye=eye) + self.assertTrue(torch.allclose(R, R_eye, atol=2e-7)) + self.assertTrue(torch.allclose(t, t_eye, atol=2e-7)) + def test_camera_position_from_angles_python_scalar(self): dist = 2.7 elev = 90.0 @@ -671,3 +684,7 @@ def test_perspective_kwargs(self): vertices, fx=2.0, fy=2.0, p0x=2.5, p0y=3.5 ) self.assertTrue(torch.allclose(v1, v2)) + + +if __name__ == "__main__": + unittest.main() From 6c412f88be049805607fddfcac2a1f12ad91fa0a Mon Sep 17 00:00:00 2001 From: Dave Greenwood Date: Mon, 24 Feb 2020 09:33:42 +0000 Subject: [PATCH 04/10] remove unnecessary main --- tests/test_cameras.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 2fb65cc08..4c55e741e 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -684,7 +684,3 @@ def test_perspective_kwargs(self): vertices, fx=2.0, fy=2.0, p0x=2.5, p0y=3.5 ) self.assertTrue(torch.allclose(v1, v2)) - - -if __name__ == "__main__": - unittest.main() From 8ab1d568d9274d7c460fcc5fd4987db22421d08d Mon Sep 17 00:00:00 2001 From: Dave Greenwood Date: Mon, 24 Feb 2020 10:53:07 +0000 Subject: [PATCH 05/10] default values for dist, elev, azim --- pytorch3d/renderer/cameras.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytorch3d/renderer/cameras.py b/pytorch3d/renderer/cameras.py index 92a891238..dead3ea81 100644 --- a/pytorch3d/renderer/cameras.py +++ b/pytorch3d/renderer/cameras.py @@ -1003,9 +1003,9 @@ def look_at_rotation( def look_at_view_transform( - dist, - elev, - azim, + dist=1.0, + elev=0.0, + azim=0.0, degrees: bool = True, eye: Sequence = None, at=((0, 0, 0),), # (1, 3) From 543464625dac8ae0c7e60b9ce311eb1fa0296761 Mon Sep 17 00:00:00 2001 From: Dave Greenwood Date: Mon, 24 Feb 2020 11:45:18 +0000 Subject: [PATCH 06/10] include eye only in test --- tests/test_cameras.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 4c55e741e..630537951 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -119,16 +119,21 @@ def setUp(self) -> None: np.random.seed(42) def test_look_at_view_transform_from_eye_point_tuple(self): + """Test passing eye tuple with and without dist, elev, angle.""" dist = math.sqrt(2) elev = math.pi / 4 azim = 0.0 eye = ((0.0, 1.0, -1.0), ) + # using passed values for dist, elev, azim R, t = look_at_view_transform(dist, elev, azim, degrees=False) - # using other values for dist, elev, azim - R_eye, t_eye = look_at_view_transform( - dist=3, elev=2, azim=1, eye=eye) + # using other values for dist, elev, azim - eye overrides + R_eye, t_eye = look_at_view_transform(dist=3, elev=2, azim=1, eye=eye) + # using only eye value + R_eye_only, t_eye_only = look_at_view_transform(eye=eye) self.assertTrue(torch.allclose(R, R_eye, atol=2e-7)) self.assertTrue(torch.allclose(t, t_eye, atol=2e-7)) + self.assertTrue(torch.allclose(R, R_eye_only, atol=2e-7)) + self.assertTrue(torch.allclose(t, t_eye_only, atol=2e-7)) def test_camera_position_from_angles_python_scalar(self): dist = 2.7 From bfc49122e97439b127bb2cf78ea1780e6ea61c77 Mon Sep 17 00:00:00 2001 From: Dave Greenwood Date: Mon, 24 Feb 2020 11:56:52 +0000 Subject: [PATCH 07/10] just use inline comment --- tests/test_cameras.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 630537951..134063aa5 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -119,7 +119,6 @@ def setUp(self) -> None: np.random.seed(42) def test_look_at_view_transform_from_eye_point_tuple(self): - """Test passing eye tuple with and without dist, elev, angle.""" dist = math.sqrt(2) elev = math.pi / 4 azim = 0.0 From 137df2aba23b83413016c936635afd7f4ef86ae2 Mon Sep 17 00:00:00 2001 From: Dave Greenwood Date: Mon, 24 Feb 2020 11:58:34 +0000 Subject: [PATCH 08/10] test default values --- tests/test_cameras.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 134063aa5..903542ae4 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -134,6 +134,23 @@ def test_look_at_view_transform_from_eye_point_tuple(self): self.assertTrue(torch.allclose(R, R_eye_only, atol=2e-7)) self.assertTrue(torch.allclose(t, t_eye_only, atol=2e-7)) + def test_look_at_view_transform_default_values(self): + dist = 1.0 + elev = 0.0 + azim = 0.0 + # Using passed values for dist, elev, azim + R, t = look_at_view_transform(dist, elev, azim) + # Using default dist=1.0, elev=0.0, azim=0.0 + R_default, t_default = look_at_view_transform() + # R should be identity, t should be (0, 0, 1) + R_expected = torch.tensor([np.eye(3)], dtype=torch.float32) + t_expected = torch.tensor([[0.0, 0.0, 1.0]]) + # test default = passed = expected + self.assertTrue(torch.allclose(R, R_default, atol=2e-7)) + self.assertTrue(torch.allclose(t, t_default, atol=2e-7)) + self.assertTrue(torch.allclose(R, R_expected, atol=2e-7)) + self.assertTrue(torch.allclose(t, t_expected, atol=2e-7)) + def test_camera_position_from_angles_python_scalar(self): dist = 2.7 elev = 90.0 From c7663fbaabec587f214b4afb83e184c2044701f8 Mon Sep 17 00:00:00 2001 From: Dave Greenwood Date: Fri, 6 Mar 2020 15:21:59 +0000 Subject: [PATCH 09/10] remove unnecessary tests --- tests/test_cameras.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 903542ae4..01cf53dd6 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -142,14 +142,9 @@ def test_look_at_view_transform_default_values(self): R, t = look_at_view_transform(dist, elev, azim) # Using default dist=1.0, elev=0.0, azim=0.0 R_default, t_default = look_at_view_transform() - # R should be identity, t should be (0, 0, 1) - R_expected = torch.tensor([np.eye(3)], dtype=torch.float32) - t_expected = torch.tensor([[0.0, 0.0, 1.0]]) # test default = passed = expected self.assertTrue(torch.allclose(R, R_default, atol=2e-7)) self.assertTrue(torch.allclose(t, t_default, atol=2e-7)) - self.assertTrue(torch.allclose(R, R_expected, atol=2e-7)) - self.assertTrue(torch.allclose(t, t_expected, atol=2e-7)) def test_camera_position_from_angles_python_scalar(self): dist = 2.7 From 2b9081070a0998e2313240117cedefd50677063f Mon Sep 17 00:00:00 2001 From: Dave Greenwood Date: Tue, 17 Mar 2020 09:55:48 +0000 Subject: [PATCH 10/10] default value for z is now positive --- tests/test_cameras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 01cf53dd6..096c1dfa7 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -122,7 +122,7 @@ def test_look_at_view_transform_from_eye_point_tuple(self): dist = math.sqrt(2) elev = math.pi / 4 azim = 0.0 - eye = ((0.0, 1.0, -1.0), ) + eye = ((0.0, 1.0, 1.0), ) # using passed values for dist, elev, azim R, t = look_at_view_transform(dist, elev, azim, degrees=False) # using other values for dist, elev, azim - eye overrides