1
1
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
2
2
3
3
import unittest
4
+ from math import radians
4
5
5
6
import torch
6
7
from common_testing import TestCaseMixin
7
- from pytorch3d .renderer .camera_utils import camera_to_eye_at_up
8
- from pytorch3d .renderer .cameras import PerspectiveCameras , look_at_view_transform
8
+ from pytorch3d .renderer .camera_utils import camera_to_eye_at_up , rotate_on_spot
9
+ from pytorch3d .renderer .cameras import (
10
+ PerspectiveCameras ,
11
+ get_world_to_view_transform ,
12
+ look_at_view_transform ,
13
+ )
14
+ from pytorch3d .transforms import axis_angle_to_matrix
9
15
from torch .nn .functional import normalize
10
16
11
17
18
+ def _batched_dotprod (x : torch .Tensor , y : torch .Tensor ):
19
+ """
20
+ Takes two tensors of shape (N,3) and returns their batched
21
+ dot product along the last dimension as a tensor of shape
22
+ (N,).
23
+ """
24
+ return torch .einsum ("ij,ij->i" , x , y )
25
+
26
+
12
27
class TestCameraUtils (TestCaseMixin , unittest .TestCase ):
13
28
def setUp (self ) -> None :
14
29
torch .manual_seed (42 )
@@ -28,6 +43,7 @@ def test_invert_eye_at_up(self):
28
43
29
44
# The retrieved eye matches
30
45
self .assertClose (eye , eye2 , atol = 1e-5 )
46
+ self .assertClose (cameras .get_camera_center (), eye )
31
47
32
48
# at-eye as retrieved must be a vector in the same direction as
33
49
# the original.
@@ -49,3 +65,99 @@ def test_invert_eye_at_up(self):
49
65
cam_trans2 = cameras2 .get_world_to_view_transform ()
50
66
51
67
self .assertClose (cam_trans .get_matrix (), cam_trans2 .get_matrix (), atol = 1e-5 )
68
+
69
+ def test_rotate_on_spot_yaw (self ):
70
+ N = 14
71
+ eye = torch .rand (N , 3 )
72
+ at = torch .rand (N , 3 )
73
+ up = torch .rand (N , 3 )
74
+
75
+ R , T = look_at_view_transform (eye = eye , at = at , up = up )
76
+
77
+ # Moving around the y axis looks left.
78
+ angles = torch .FloatTensor ([0 , - radians (10 ), 0 ])
79
+ rotation = axis_angle_to_matrix (angles )
80
+ R_rot , T_rot = rotate_on_spot (R , T , rotation )
81
+
82
+ eye_rot , at_rot , up_rot = camera_to_eye_at_up (
83
+ get_world_to_view_transform (R = R_rot , T = T_rot )
84
+ )
85
+ self .assertClose (eye , eye_rot , atol = 1e-5 )
86
+
87
+ # Make vectors pointing exactly left and up
88
+ left = torch .cross (up , at - eye , dim = - 1 )
89
+ left_rot = torch .cross (up_rot , at_rot - eye_rot , dim = - 1 )
90
+ fully_up = torch .cross (at - eye , left , dim = - 1 )
91
+ fully_up_rot = torch .cross (at_rot - eye_rot , left_rot , dim = - 1 )
92
+
93
+ # The up direction is unchanged
94
+ self .assertClose (normalize (fully_up ), normalize (fully_up_rot ), atol = 1e-5 )
95
+
96
+ # The camera has moved left
97
+ agree = _batched_dotprod (torch .cross (left , left_rot , dim = 1 ), fully_up )
98
+ self .assertGreater (agree .min (), 0 )
99
+
100
+ # Batch dimension for rotation
101
+ R_rot2 , T_rot2 = rotate_on_spot (R , T , rotation .expand (N , 3 , 3 ))
102
+ self .assertClose (R_rot , R_rot2 )
103
+ self .assertClose (T_rot , T_rot2 )
104
+
105
+ # No batch dimension for either
106
+ R_rot3 , T_rot3 = rotate_on_spot (R [0 ], T [0 ], rotation )
107
+ self .assertClose (R_rot [:1 ], R_rot3 )
108
+ self .assertClose (T_rot [:1 ], T_rot3 )
109
+
110
+ # No batch dimension for R, T
111
+ R_rot4 , T_rot4 = rotate_on_spot (R [0 ], T [0 ], rotation .expand (N , 3 , 3 ))
112
+ self .assertClose (R_rot [:1 ].expand (N , 3 , 3 ), R_rot4 )
113
+ self .assertClose (T_rot [:1 ].expand (N , 3 ), T_rot4 )
114
+
115
+ def test_rotate_on_spot_pitch (self ):
116
+ N = 14
117
+ eye = torch .rand (N , 3 )
118
+ at = torch .rand (N , 3 )
119
+ up = torch .rand (N , 3 )
120
+
121
+ R , T = look_at_view_transform (eye = eye , at = at , up = up )
122
+
123
+ # Moving around the x axis looks down.
124
+ angles = torch .FloatTensor ([- radians (10 ), 0 , 0 ])
125
+ rotation = axis_angle_to_matrix (angles )
126
+ R_rot , T_rot = rotate_on_spot (R , T , rotation )
127
+ eye_rot , at_rot , up_rot = camera_to_eye_at_up (
128
+ get_world_to_view_transform (R = R_rot , T = T_rot )
129
+ )
130
+ self .assertClose (eye , eye_rot , atol = 1e-5 )
131
+
132
+ # A vector pointing left is unchanged
133
+ left = torch .cross (up , at - eye , dim = - 1 )
134
+ left_rot = torch .cross (up_rot , at_rot - eye_rot , dim = - 1 )
135
+ self .assertClose (normalize (left ), normalize (left_rot ), atol = 1e-5 )
136
+
137
+ # The camera has moved down
138
+ fully_up = torch .cross (at - eye , left , dim = - 1 )
139
+ fully_up_rot = torch .cross (at_rot - eye_rot , left_rot , dim = - 1 )
140
+ agree = _batched_dotprod (torch .cross (fully_up , fully_up_rot , dim = 1 ), left )
141
+ self .assertGreater (agree .min (), 0 )
142
+
143
+ def test_rotate_on_spot_roll (self ):
144
+ N = 14
145
+ eye = torch .rand (N , 3 )
146
+ at = torch .rand (N , 3 )
147
+ up = torch .rand (N , 3 )
148
+
149
+ R , T = look_at_view_transform (eye = eye , at = at , up = up )
150
+
151
+ # Moving around the z axis rotates the image.
152
+ angles = torch .FloatTensor ([0 , 0 , - radians (10 )])
153
+ rotation = axis_angle_to_matrix (angles )
154
+ R_rot , T_rot = rotate_on_spot (R , T , rotation )
155
+ eye_rot , at_rot , up_rot = camera_to_eye_at_up (
156
+ get_world_to_view_transform (R = R_rot , T = T_rot )
157
+ )
158
+ self .assertClose (eye , eye_rot , atol = 1e-5 )
159
+ self .assertClose (normalize (at - eye ), normalize (at_rot - eye ), atol = 1e-5 )
160
+
161
+ # The camera has moved clockwise
162
+ agree = _batched_dotprod (torch .cross (up , up_rot , dim = 1 ), at - eye )
163
+ self .assertGreater (agree .min (), 0 )
0 commit comments