test_cameras.py 37.1 KB
Newer Older
facebook-github-bot's avatar
facebook-github-bot committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.


# Some of the code below is adapted from Soft Rasterizer (SoftRas)
#
# Copyright (c) 2017 Hiroharu Kato
# Copyright (c) 2018 Nikos Kolotouros
# Copyright (c) 2019 Shichen Liu
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import math
import unittest

31
32
33
import numpy as np
import torch
from common_testing import TestCaseMixin
Georgia Gkioxari's avatar
Georgia Gkioxari committed
34
35
36
37
from pytorch3d.renderer.cameras import OpenGLOrthographicCameras  # deprecated
from pytorch3d.renderer.cameras import OpenGLPerspectiveCameras  # deprecated
from pytorch3d.renderer.cameras import SfMOrthographicCameras  # deprecated
from pytorch3d.renderer.cameras import SfMPerspectiveCameras  # deprecated
facebook-github-bot's avatar
facebook-github-bot committed
38
from pytorch3d.renderer.cameras import (
39
    CamerasBase,
Georgia Gkioxari's avatar
Georgia Gkioxari committed
40
41
42
43
    FoVOrthographicCameras,
    FoVPerspectiveCameras,
    OrthographicCameras,
    PerspectiveCameras,
facebook-github-bot's avatar
facebook-github-bot committed
44
45
46
    camera_position_from_spherical_angles,
    get_world_to_view_transform,
    look_at_rotation,
47
    look_at_view_transform,
facebook-github-bot's avatar
facebook-github-bot committed
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
)
from pytorch3d.transforms import Transform3d
from pytorch3d.transforms.so3 import so3_exponential_map


# Naive function adapted from SoftRasterizer for test purposes.
def perspective_project_naive(points, fov=60.0):
    """
    Compute perspective projection from a given viewing angle.
    Args:
        points: (N, V, 3) representing the padded points.
        viewing angle: degrees
    Returns:
        (N, V, 3) tensor of projected points preserving the view space z
        coordinate (no z renormalization)
    """
    device = points.device
65
    halfFov = torch.tensor((fov / 2) / 180 * np.pi, dtype=torch.float32, device=device)
facebook-github-bot's avatar
facebook-github-bot committed
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
    scale = torch.tan(halfFov[None])
    scale = scale[:, None]
    z = points[:, :, 2]
    x = points[:, :, 0] / z / scale
    y = points[:, :, 1] / z / scale
    points = torch.stack((x, y, z), dim=2)
    return points


def sfm_perspective_project_naive(points, fx=1.0, fy=1.0, p0x=0.0, p0y=0.0):
    """
    Compute perspective projection using focal length and principal point.

    Args:
        points: (N, V, 3) representing the padded points.
        fx: world units
        fy: world units
        p0x: pixels
        p0y: pixels
    Returns:
        (N, V, 3) tensor of projected points.
    """
    z = points[:, :, 2]
89
90
    x = (points[:, :, 0] * fx) / z + p0x
    y = (points[:, :, 1] * fy) / z + p0y
facebook-github-bot's avatar
facebook-github-bot committed
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
    points = torch.stack((x, y, 1.0 / z), dim=2)
    return points


# Naive function adapted from SoftRasterizer for test purposes.
def orthographic_project_naive(points, scale_xyz=(1.0, 1.0, 1.0)):
    """
    Compute orthographic projection from a given angle
    Args:
        points: (N, V, 3) representing the padded points.
        scaled: (N, 3) scaling factors for each of xyz directions
    Returns:
        (N, V, 3) tensor of projected points preserving the view space z
        coordinate (no z renormalization).
    """
    if not torch.is_tensor(scale_xyz):
        scale_xyz = torch.tensor(scale_xyz)
    scale_xyz = scale_xyz.view(-1, 3)
    z = points[:, :, 2]
    x = points[:, :, 0] * scale_xyz[:, 0]
    y = points[:, :, 1] * scale_xyz[:, 1]
    points = torch.stack((x, y, z), dim=2)
    return points


Georgia Gkioxari's avatar
Georgia Gkioxari committed
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def ndc_to_screen_points_naive(points, imsize):
    """
    Transforms points from PyTorch3D's NDC space to screen space
    Args:
        points: (N, V, 3) representing padded points
        imsize: (N, 2) image size = (width, height)
    Returns:
        (N, V, 3) tensor of transformed points
    """
    imwidth, imheight = imsize.unbind(1)
    imwidth = imwidth.view(-1, 1)
    imheight = imheight.view(-1, 1)

    x, y, z = points.unbind(2)
    x = (1.0 - x) * (imwidth - 1) / 2.0
    y = (1.0 - y) * (imheight - 1) / 2.0
    return torch.stack((x, y, z), dim=2)


Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
135
class TestCameraHelpers(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
136
137
138
139
140
    def setUp(self) -> None:
        super().setUp()
        torch.manual_seed(42)
        np.random.seed(42)

141
142
143
144
    def test_look_at_view_transform_from_eye_point_tuple(self):
        dist = math.sqrt(2)
        elev = math.pi / 4
        azim = 0.0
Georgia Gkioxari's avatar
Georgia Gkioxari committed
145
        eye = ((0.0, 1.0, 1.0),)
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
        # 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
        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_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()
        # test default = passed = expected
        self.assertTrue(torch.allclose(R, R_default, atol=2e-7))
        self.assertTrue(torch.allclose(t, t_default, atol=2e-7))

facebook-github-bot's avatar
facebook-github-bot committed
170
171
172
173
    def test_camera_position_from_angles_python_scalar(self):
        dist = 2.7
        elev = 90.0
        azim = 0.0
174
175
176
        expected_position = torch.tensor([0.0, 2.7, 0.0], dtype=torch.float32).view(
            1, 3
        )
facebook-github-bot's avatar
facebook-github-bot committed
177
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
178
        self.assertClose(position, expected_position, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
179
180
181
182
183
184
185
186
187
188

    def test_camera_position_from_angles_python_scalar_radians(self):
        dist = 2.7
        elev = math.pi / 2
        azim = 0.0
        expected_position = torch.tensor([0.0, 2.7, 0.0], dtype=torch.float32)
        expected_position = expected_position.view(1, 3)
        position = camera_position_from_spherical_angles(
            dist, elev, azim, degrees=False
        )
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
189
        self.assertClose(position, expected_position, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
190
191
192
193
194

    def test_camera_position_from_angles_torch_scalars(self):
        dist = torch.tensor(2.7)
        elev = torch.tensor(0.0)
        azim = torch.tensor(90.0)
195
196
197
        expected_position = torch.tensor([2.7, 0.0, 0.0], dtype=torch.float32).view(
            1, 3
        )
facebook-github-bot's avatar
facebook-github-bot committed
198
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
199
        self.assertClose(position, expected_position, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
200
201
202
203
204

    def test_camera_position_from_angles_mixed_scalars(self):
        dist = 2.7
        elev = torch.tensor(0.0)
        azim = 90.0
205
206
207
        expected_position = torch.tensor([2.7, 0.0, 0.0], dtype=torch.float32).view(
            1, 3
        )
facebook-github-bot's avatar
facebook-github-bot committed
208
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
209
        self.assertClose(position, expected_position, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

    def test_camera_position_from_angles_torch_scalar_grads(self):
        dist = torch.tensor(2.7, requires_grad=True)
        elev = torch.tensor(45.0, requires_grad=True)
        azim = torch.tensor(45.0)
        position = camera_position_from_spherical_angles(dist, elev, azim)
        position.sum().backward()
        self.assertTrue(hasattr(elev, "grad"))
        self.assertTrue(hasattr(dist, "grad"))
        elev_grad = elev.grad.clone()
        dist_grad = dist.grad.clone()
        elev = math.pi / 180.0 * elev.detach()
        azim = math.pi / 180.0 * azim
        grad_dist = (
            torch.cos(elev) * torch.sin(azim)
            + torch.sin(elev)
226
            + torch.cos(elev) * torch.cos(azim)
facebook-github-bot's avatar
facebook-github-bot committed
227
228
        )
        grad_elev = (
Nikhila Ravi's avatar
Nikhila Ravi committed
229
            -(torch.sin(elev)) * torch.sin(azim)
facebook-github-bot's avatar
facebook-github-bot committed
230
            + torch.cos(elev)
231
            - torch.sin(elev) * torch.cos(azim)
facebook-github-bot's avatar
facebook-github-bot committed
232
233
        )
        grad_elev = dist * (math.pi / 180.0) * grad_elev
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
234
235
        self.assertClose(elev_grad, grad_elev)
        self.assertClose(dist_grad, grad_dist)
facebook-github-bot's avatar
facebook-github-bot committed
236
237
238
239
240
241
242
243
244

    def test_camera_position_from_angles_vectors(self):
        dist = torch.tensor([2.0, 2.0])
        elev = torch.tensor([0.0, 90.0])
        azim = torch.tensor([90.0, 0.0])
        expected_position = torch.tensor(
            [[2.0, 0.0, 0.0], [0.0, 2.0, 0.0]], dtype=torch.float32
        )
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
245
        self.assertClose(position, expected_position, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
246
247
248
249
250
251

    def test_camera_position_from_angles_vectors_broadcast(self):
        dist = torch.tensor([2.0, 3.0, 5.0])
        elev = torch.tensor([0.0])
        azim = torch.tensor([90.0])
        expected_position = torch.tensor(
252
            [[2.0, 0.0, 0.0], [3.0, 0.0, 0.0], [5.0, 0.0, 0.0]], dtype=torch.float32
facebook-github-bot's avatar
facebook-github-bot committed
253
254
        )
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
255
        self.assertClose(position, expected_position, atol=3e-7)
facebook-github-bot's avatar
facebook-github-bot committed
256
257
258
259
260
261

    def test_camera_position_from_angles_vectors_mixed_broadcast(self):
        dist = torch.tensor([2.0, 3.0, 5.0])
        elev = 0.0
        azim = torch.tensor(90.0)
        expected_position = torch.tensor(
262
            [[2.0, 0.0, 0.0], [3.0, 0.0, 0.0], [5.0, 0.0, 0.0]], dtype=torch.float32
facebook-github-bot's avatar
facebook-github-bot committed
263
264
        )
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
265
        self.assertClose(position, expected_position, atol=3e-7)
facebook-github-bot's avatar
facebook-github-bot committed
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282

    def test_camera_position_from_angles_vectors_mixed_broadcast_grads(self):
        dist = torch.tensor([2.0, 3.0, 5.0], requires_grad=True)
        elev = torch.tensor(45.0, requires_grad=True)
        azim = 45.0
        position = camera_position_from_spherical_angles(dist, elev, azim)
        position.sum().backward()
        self.assertTrue(hasattr(elev, "grad"))
        self.assertTrue(hasattr(dist, "grad"))
        elev_grad = elev.grad.clone()
        dist_grad = dist.grad.clone()
        azim = torch.tensor(azim)
        elev = math.pi / 180.0 * elev.detach()
        azim = math.pi / 180.0 * azim
        grad_dist = (
            torch.cos(elev) * torch.sin(azim)
            + torch.sin(elev)
283
            + torch.cos(elev) * torch.cos(azim)
facebook-github-bot's avatar
facebook-github-bot committed
284
285
        )
        grad_elev = (
Nikhila Ravi's avatar
Nikhila Ravi committed
286
            -(torch.sin(elev)) * torch.sin(azim)
facebook-github-bot's avatar
facebook-github-bot committed
287
            + torch.cos(elev)
288
            - torch.sin(elev) * torch.cos(azim)
facebook-github-bot's avatar
facebook-github-bot committed
289
290
        )
        grad_elev = (dist * (math.pi / 180.0) * grad_elev).sum()
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
291
292
        self.assertClose(elev_grad, grad_elev)
        self.assertClose(dist_grad, torch.full([3], grad_dist))
facebook-github-bot's avatar
facebook-github-bot committed
293
294
295
296
297
298
299
300
301
302
303
304

    def test_camera_position_from_angles_vectors_bad_broadcast(self):
        # Batch dim for broadcast must be N or 1
        dist = torch.tensor([2.0, 3.0, 5.0])
        elev = torch.tensor([0.0, 90.0])
        azim = torch.tensor([90.0])
        with self.assertRaises(ValueError):
            camera_position_from_spherical_angles(dist, elev, azim)

    def test_look_at_rotation_python_list(self):
        camera_position = [[0.0, 0.0, -1.0]]  # camera pointing along negative z
        rot_mat = look_at_rotation(camera_position)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
305
        self.assertClose(rot_mat, torch.eye(3)[None], atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331

    def test_look_at_rotation_input_fail(self):
        camera_position = [-1.0]  # expected to have xyz positions
        with self.assertRaises(ValueError):
            look_at_rotation(camera_position)

    def test_look_at_rotation_list_broadcast(self):
        # fmt: off
        camera_positions = [[0.0, 0.0, -1.0], [0.0, 0.0, 1.0]]
        rot_mats_expected = torch.tensor(
            [
                [
                    [1.0, 0.0, 0.0],
                    [0.0, 1.0, 0.0],
                    [0.0, 0.0, 1.0]
                ],
                [
                    [-1.0, 0.0,  0.0],  # noqa: E241, E201
                    [ 0.0, 1.0,  0.0],  # noqa: E241, E201
                    [ 0.0, 0.0, -1.0]   # noqa: E241, E201
                ],
            ],
            dtype=torch.float32
        )
        # fmt: on
        rot_mats = look_at_rotation(camera_positions)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
332
        self.assertClose(rot_mats, rot_mats_expected, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356

    def test_look_at_rotation_tensor_broadcast(self):
        # fmt: off
        camera_positions = torch.tensor([
            [0.0, 0.0, -1.0],
            [0.0, 0.0,  1.0]   # noqa: E241, E201
        ], dtype=torch.float32)
        rot_mats_expected = torch.tensor(
            [
                [
                    [1.0, 0.0, 0.0],
                    [0.0, 1.0, 0.0],
                    [0.0, 0.0, 1.0]
                ],
                [
                    [-1.0, 0.0,  0.0],  # noqa: E241, E201
                    [ 0.0, 1.0,  0.0],  # noqa: E241, E201
                    [ 0.0, 0.0, -1.0]   # noqa: E241, E201
                ],
            ],
            dtype=torch.float32
        )
        # fmt: on
        rot_mats = look_at_rotation(camera_positions)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
357
        self.assertClose(rot_mats, rot_mats_expected, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
358
359
360
361
362
363

    def test_look_at_rotation_tensor_grad(self):
        camera_position = torch.tensor([[0.0, 0.0, -1.0]], requires_grad=True)
        rot_mat = look_at_rotation(camera_position)
        rot_mat.sum().backward()
        self.assertTrue(hasattr(camera_position, "grad"))
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
364
365
        self.assertClose(
            camera_position.grad, torch.zeros_like(camera_position), atol=2e-7
facebook-github-bot's avatar
facebook-github-bot committed
366
367
368
369
370
371
372
373
        )

    def test_view_transform(self):
        T = torch.tensor([0.0, 0.0, -1.0], requires_grad=True).view(1, -1)
        R = look_at_rotation(T)
        RT = get_world_to_view_transform(R=R, T=T)
        self.assertTrue(isinstance(RT, Transform3d))

374
375

class TestCamerasCommon(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
376
377
378
379
380
381
382
383
384
    def test_view_transform_class_method(self):
        T = torch.tensor([0.0, 0.0, -1.0], requires_grad=True).view(1, -1)
        R = look_at_rotation(T)
        RT = get_world_to_view_transform(R=R, T=T)
        for cam_type in (
            OpenGLPerspectiveCameras,
            OpenGLOrthographicCameras,
            SfMOrthographicCameras,
            SfMPerspectiveCameras,
Georgia Gkioxari's avatar
Georgia Gkioxari committed
385
386
387
388
            FoVOrthographicCameras,
            FoVPerspectiveCameras,
            OrthographicCameras,
            PerspectiveCameras,
facebook-github-bot's avatar
facebook-github-bot committed
389
390
391
        ):
            cam = cam_type(R=R, T=T)
            RT_class = cam.get_world_to_view_transform()
392
            self.assertTrue(torch.allclose(RT.get_matrix(), RT_class.get_matrix()))
facebook-github-bot's avatar
facebook-github-bot committed
393
394
395
396
397
398
399
400
401
402
403

        self.assertTrue(isinstance(RT, Transform3d))

    def test_get_camera_center(self, batch_size=10):
        T = torch.randn(batch_size, 3)
        R = so3_exponential_map(torch.randn(batch_size, 3) * 3.0)
        for cam_type in (
            OpenGLPerspectiveCameras,
            OpenGLOrthographicCameras,
            SfMOrthographicCameras,
            SfMPerspectiveCameras,
Georgia Gkioxari's avatar
Georgia Gkioxari committed
404
405
406
407
            FoVOrthographicCameras,
            FoVPerspectiveCameras,
            OrthographicCameras,
            PerspectiveCameras,
facebook-github-bot's avatar
facebook-github-bot committed
408
409
410
411
412
413
        ):
            cam = cam_type(R=R, T=T)
            C = cam.get_camera_center()
            C_ = -torch.bmm(R, T[:, :, None])[:, :, 0]
            self.assertTrue(torch.allclose(C, C_, atol=1e-05))

414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
    @staticmethod
    def init_random_cameras(cam_type: CamerasBase, batch_size: int):
        cam_params = {}
        T = torch.randn(batch_size, 3) * 0.03
        T[:, 2] = 4
        R = so3_exponential_map(torch.randn(batch_size, 3) * 3.0)
        cam_params = {"R": R, "T": T}
        if cam_type in (OpenGLPerspectiveCameras, OpenGLOrthographicCameras):
            cam_params["znear"] = torch.rand(batch_size) * 10 + 0.1
            cam_params["zfar"] = torch.rand(batch_size) * 4 + 1 + cam_params["znear"]
            if cam_type == OpenGLPerspectiveCameras:
                cam_params["fov"] = torch.rand(batch_size) * 60 + 30
                cam_params["aspect_ratio"] = torch.rand(batch_size) * 0.5 + 0.5
            else:
                cam_params["top"] = torch.rand(batch_size) * 0.2 + 0.9
Nikhila Ravi's avatar
Nikhila Ravi committed
429
430
                cam_params["bottom"] = -(torch.rand(batch_size)) * 0.2 - 0.9
                cam_params["left"] = -(torch.rand(batch_size)) * 0.2 - 0.9
431
                cam_params["right"] = torch.rand(batch_size) * 0.2 + 0.9
Georgia Gkioxari's avatar
Georgia Gkioxari committed
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
        elif cam_type in (FoVPerspectiveCameras, FoVOrthographicCameras):
            cam_params["znear"] = torch.rand(batch_size) * 10 + 0.1
            cam_params["zfar"] = torch.rand(batch_size) * 4 + 1 + cam_params["znear"]
            if cam_type == FoVPerspectiveCameras:
                cam_params["fov"] = torch.rand(batch_size) * 60 + 30
                cam_params["aspect_ratio"] = torch.rand(batch_size) * 0.5 + 0.5
            else:
                cam_params["max_y"] = torch.rand(batch_size) * 0.2 + 0.9
                cam_params["min_y"] = -(torch.rand(batch_size)) * 0.2 - 0.9
                cam_params["min_x"] = -(torch.rand(batch_size)) * 0.2 - 0.9
                cam_params["max_x"] = torch.rand(batch_size) * 0.2 + 0.9
        elif cam_type in (
            SfMOrthographicCameras,
            SfMPerspectiveCameras,
            OrthographicCameras,
            PerspectiveCameras,
        ):
449
450
            cam_params["focal_length"] = torch.rand(batch_size) * 10 + 0.1
            cam_params["principal_point"] = torch.randn((batch_size, 2))
Georgia Gkioxari's avatar
Georgia Gkioxari committed
451

452
453
454
455
        else:
            raise ValueError(str(cam_type))
        return cam_type(**cam_params)

Georgia Gkioxari's avatar
Georgia Gkioxari committed
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
    @staticmethod
    def init_equiv_cameras_ndc_screen(cam_type: CamerasBase, batch_size: int):
        T = torch.randn(batch_size, 3) * 0.03
        T[:, 2] = 4
        R = so3_exponential_map(torch.randn(batch_size, 3) * 3.0)
        screen_cam_params = {"R": R, "T": T}
        ndc_cam_params = {"R": R, "T": T}
        if cam_type in (OrthographicCameras, PerspectiveCameras):
            ndc_cam_params["focal_length"] = torch.rand((batch_size, 2)) * 3.0
            ndc_cam_params["principal_point"] = torch.randn((batch_size, 2))

            image_size = torch.randint(low=2, high=64, size=(batch_size, 2))
            screen_cam_params["image_size"] = image_size
            screen_cam_params["focal_length"] = (
                ndc_cam_params["focal_length"] * image_size / 2.0
            )
            screen_cam_params["principal_point"] = (
                (1.0 - ndc_cam_params["principal_point"]) * image_size / 2.0
            )
        else:
            raise ValueError(str(cam_type))
        return cam_type(**ndc_cam_params), cam_type(**screen_cam_params)

479
480
481
482
483
484
485
486
487
488
489
    def test_unproject_points(self, batch_size=50, num_points=100):
        """
        Checks that an unprojection of a randomly projected point cloud
        stays the same.
        """

        for cam_type in (
            SfMOrthographicCameras,
            OpenGLPerspectiveCameras,
            OpenGLOrthographicCameras,
            SfMPerspectiveCameras,
Georgia Gkioxari's avatar
Georgia Gkioxari committed
490
491
492
493
            FoVOrthographicCameras,
            FoVPerspectiveCameras,
            OrthographicCameras,
            PerspectiveCameras,
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
        ):
            # init the cameras
            cameras = TestCamerasCommon.init_random_cameras(cam_type, batch_size)
            # xyz - the ground truth point cloud
            xyz = torch.randn(batch_size, num_points, 3) * 0.3
            # xyz in camera coordinates
            xyz_cam = cameras.get_world_to_view_transform().transform_points(xyz)
            # depth = z-component of xyz_cam
            depth = xyz_cam[:, :, 2:]
            # project xyz
            xyz_proj = cameras.transform_points(xyz)
            xy, cam_depth = xyz_proj.split(2, dim=2)
            # input to the unprojection function
            xy_depth = torch.cat((xy, depth), dim=2)

            for to_world in (False, True):
                if to_world:
                    matching_xyz = xyz
                else:
                    matching_xyz = xyz_cam

Georgia Gkioxari's avatar
Georgia Gkioxari committed
515
                # if we have FoV (= OpenGL) cameras
516
                # test for scaled_depth_input=True/False
Georgia Gkioxari's avatar
Georgia Gkioxari committed
517
518
519
520
521
522
                if cam_type in (
                    OpenGLPerspectiveCameras,
                    OpenGLOrthographicCameras,
                    FoVPerspectiveCameras,
                    FoVOrthographicCameras,
                ):
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
                    for scaled_depth_input in (True, False):
                        if scaled_depth_input:
                            xy_depth_ = xyz_proj
                        else:
                            xy_depth_ = xy_depth
                        xyz_unproj = cameras.unproject_points(
                            xy_depth_,
                            world_coordinates=to_world,
                            scaled_depth_input=scaled_depth_input,
                        )
                        self.assertTrue(
                            torch.allclose(xyz_unproj, matching_xyz, atol=1e-4)
                        )
                else:
                    xyz_unproj = cameras.unproject_points(
                        xy_depth, world_coordinates=to_world
                    )
                    self.assertTrue(torch.allclose(xyz_unproj, matching_xyz, atol=1e-4))

Georgia Gkioxari's avatar
Georgia Gkioxari committed
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
    def test_project_points_screen(self, batch_size=50, num_points=100):
        """
        Checks that an unprojection of a randomly projected point cloud
        stays the same.
        """

        for cam_type in (
            OpenGLOrthographicCameras,
            OpenGLPerspectiveCameras,
            SfMOrthographicCameras,
            SfMPerspectiveCameras,
            FoVOrthographicCameras,
            FoVPerspectiveCameras,
            OrthographicCameras,
            PerspectiveCameras,
        ):

            # init the cameras
            cameras = TestCamerasCommon.init_random_cameras(cam_type, batch_size)
            # xyz - the ground truth point cloud
            xyz = torch.randn(batch_size, num_points, 3) * 0.3
            # image size
            image_size = torch.randint(low=2, high=64, size=(batch_size, 2))
            # project points
            xyz_project_ndc = cameras.transform_points(xyz)
            xyz_project_screen = cameras.transform_points_screen(xyz, image_size)
            # naive
            xyz_project_screen_naive = ndc_to_screen_points_naive(
                xyz_project_ndc, image_size
            )
            self.assertClose(xyz_project_screen, xyz_project_screen_naive)

    def test_equiv_project_points(self, batch_size=50, num_points=100):
        """
        Checks that NDC and screen cameras project points to ndc correctly.
        Applies only to OrthographicCameras and PerspectiveCameras.
        """
        for cam_type in (OrthographicCameras, PerspectiveCameras):
            # init the cameras
            (
                ndc_cameras,
                screen_cameras,
            ) = TestCamerasCommon.init_equiv_cameras_ndc_screen(cam_type, batch_size)
            # xyz - the ground truth point cloud
            xyz = torch.randn(batch_size, num_points, 3) * 0.3
            # project points
            xyz_ndc_cam = ndc_cameras.transform_points(xyz)
            xyz_screen_cam = screen_cameras.transform_points(xyz)
            self.assertClose(xyz_ndc_cam, xyz_screen_cam, atol=1e-6)

592
593
594
595
596
597
598
599
600
    def test_clone(self, batch_size: int = 10):
        """
        Checks the clone function of the cameras.
        """
        for cam_type in (
            SfMOrthographicCameras,
            OpenGLPerspectiveCameras,
            OpenGLOrthographicCameras,
            SfMPerspectiveCameras,
Georgia Gkioxari's avatar
Georgia Gkioxari committed
601
602
603
604
            FoVOrthographicCameras,
            FoVPerspectiveCameras,
            OrthographicCameras,
            PerspectiveCameras,
605
606
607
608
609
610
611
612
613
614
615
616
617
618
        ):
            cameras = TestCamerasCommon.init_random_cameras(cam_type, batch_size)
            cameras = cameras.to(torch.device("cpu"))
            cameras_clone = cameras.clone()

            for var in cameras.__dict__.keys():
                val = getattr(cameras, var)
                val_clone = getattr(cameras_clone, var)
                if torch.is_tensor(val):
                    self.assertClose(val, val_clone)
                    self.assertSeparate(val, val_clone)
                else:
                    self.assertTrue(val == val_clone)

facebook-github-bot's avatar
facebook-github-bot committed
619

Georgia Gkioxari's avatar
Georgia Gkioxari committed
620
621
622
623
624
625
############################################################
#                FoVPerspective Camera                     #
############################################################


class TestFoVPerspectiveProjection(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
626
627
628
    def test_perspective(self):
        far = 10.0
        near = 1.0
Georgia Gkioxari's avatar
Georgia Gkioxari committed
629
        cameras = FoVPerspectiveCameras(znear=near, zfar=far, fov=60.0)
facebook-github-bot's avatar
facebook-github-bot committed
630
631
632
633
634
635
636
637
638
        P = cameras.get_projection_transform()
        # vertices are at the far clipping plane so z gets mapped to 1.
        vertices = torch.tensor([1, 2, far], dtype=torch.float32)
        projected_verts = torch.tensor(
            [np.sqrt(3) / far, 2 * np.sqrt(3) / far, 1.0], dtype=torch.float32
        )
        vertices = vertices[None, None, :]
        v1 = P.transform_points(vertices)
        v2 = perspective_project_naive(vertices, fov=60.0)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
639
640
641
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(far * v1[..., 2], v2[..., 2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
642
643
644
645
646
647
648
649

        # vertices are at the near clipping plane so z gets mapped to 0.0.
        vertices[..., 2] = near
        projected_verts = torch.tensor(
            [np.sqrt(3) / near, 2 * np.sqrt(3) / near, 0.0], dtype=torch.float32
        )
        v1 = P.transform_points(vertices)
        v2 = perspective_project_naive(vertices, fov=60.0)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
650
651
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
652
653

    def test_perspective_kwargs(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
654
        cameras = FoVPerspectiveCameras(znear=5.0, zfar=100.0, fov=0.0)
facebook-github-bot's avatar
facebook-github-bot committed
655
656
657
658
659
660
661
662
663
        # Override defaults by passing in values to get_projection_transform
        far = 10.0
        P = cameras.get_projection_transform(znear=1.0, zfar=far, fov=60.0)
        vertices = torch.tensor([1, 2, far], dtype=torch.float32)
        projected_verts = torch.tensor(
            [np.sqrt(3) / far, 2 * np.sqrt(3) / far, 1.0], dtype=torch.float32
        )
        vertices = vertices[None, None, :]
        v1 = P.transform_points(vertices)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
664
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
665
666
667
668
669

    def test_perspective_mixed_inputs_broadcast(self):
        far = torch.tensor([10.0, 20.0], dtype=torch.float32)
        near = 1.0
        fov = torch.tensor(60.0)
Georgia Gkioxari's avatar
Georgia Gkioxari committed
670
        cameras = FoVPerspectiveCameras(znear=near, zfar=far, fov=fov)
facebook-github-bot's avatar
facebook-github-bot committed
671
672
673
        P = cameras.get_projection_transform()
        vertices = torch.tensor([1, 2, 10], dtype=torch.float32)
        z1 = 1.0  # vertices at far clipping plane so z = 1.0
Nikhila Ravi's avatar
Nikhila Ravi committed
674
        z2 = (20.0 / (20.0 - 1.0) * 10.0 + -20.0 / (20.0 - 1.0)) / 10.0
facebook-github-bot's avatar
facebook-github-bot committed
675
676
677
678
679
680
681
682
683
684
        projected_verts = torch.tensor(
            [
                [np.sqrt(3) / 10.0, 2 * np.sqrt(3) / 10.0, z1],
                [np.sqrt(3) / 10.0, 2 * np.sqrt(3) / 10.0, z2],
            ],
            dtype=torch.float32,
        )
        vertices = vertices[None, None, :]
        v1 = P.transform_points(vertices)
        v2 = perspective_project_naive(vertices, fov=60.0)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
685
686
        self.assertClose(v1[..., :2], torch.cat([v2, v2])[..., :2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
687
688
689
690
691

    def test_perspective_mixed_inputs_grad(self):
        far = torch.tensor([10.0])
        near = 1.0
        fov = torch.tensor(60.0, requires_grad=True)
Georgia Gkioxari's avatar
Georgia Gkioxari committed
692
        cameras = FoVPerspectiveCameras(znear=near, zfar=far, fov=fov)
facebook-github-bot's avatar
facebook-github-bot committed
693
694
695
696
697
698
699
700
701
702
703
        P = cameras.get_projection_transform()
        vertices = torch.tensor([1, 2, 10], dtype=torch.float32)
        vertices_batch = vertices[None, None, :]
        v1 = P.transform_points(vertices_batch).squeeze()
        v1.sum().backward()
        self.assertTrue(hasattr(fov, "grad"))
        fov_grad = fov.grad.clone()
        half_fov_rad = (math.pi / 180.0) * fov.detach() / 2.0
        grad_cotan = -(1.0 / (torch.sin(half_fov_rad) ** 2.0) * 1 / 2.0)
        grad_fov = (math.pi / 180.0) * grad_cotan
        grad_fov = (vertices[0] + vertices[1]) * grad_fov / 10.0
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
704
        self.assertClose(fov_grad, grad_fov)
facebook-github-bot's avatar
facebook-github-bot committed
705
706
707

    def test_camera_class_init(self):
        device = torch.device("cuda:0")
Georgia Gkioxari's avatar
Georgia Gkioxari committed
708
        cam = FoVPerspectiveCameras(znear=10.0, zfar=(100.0, 200.0))
facebook-github-bot's avatar
facebook-github-bot committed
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726

        # Check broadcasting
        self.assertTrue(cam.znear.shape == (2,))
        self.assertTrue(cam.zfar.shape == (2,))

        # update znear element 1
        cam[1].znear = 20.0
        self.assertTrue(cam.znear[1] == 20.0)

        # Get item and get value
        c0 = cam[0]
        self.assertTrue(c0.zfar == 100.0)

        # Test to
        new_cam = cam.to(device=device)
        self.assertTrue(new_cam.device == device)

    def test_get_full_transform(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
727
        cam = FoVPerspectiveCameras()
facebook-github-bot's avatar
facebook-github-bot committed
728
729
730
731
        T = torch.tensor([0.0, 0.0, 1.0]).view(1, -1)
        R = look_at_rotation(T)
        P = cam.get_full_projection_transform(R=R, T=T)
        self.assertTrue(isinstance(P, Transform3d))
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
732
733
        self.assertClose(cam.R, R)
        self.assertClose(cam.T, T)
facebook-github-bot's avatar
facebook-github-bot committed
734
735
736
737
738

    def test_transform_points(self):
        # Check transform_points methods works with default settings for
        # RT and P
        far = 10.0
Georgia Gkioxari's avatar
Georgia Gkioxari committed
739
        cam = FoVPerspectiveCameras(znear=1.0, zfar=far, fov=60.0)
facebook-github-bot's avatar
facebook-github-bot committed
740
741
742
743
744
745
746
        points = torch.tensor([1, 2, far], dtype=torch.float32)
        points = points.view(1, 1, 3).expand(5, 10, -1)
        projected_points = torch.tensor(
            [np.sqrt(3) / far, 2 * np.sqrt(3) / far, 1.0], dtype=torch.float32
        )
        projected_points = projected_points.view(1, 1, 3).expand(5, 10, -1)
        new_points = cam.transform_points(points)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
747
        self.assertClose(new_points, projected_points)
facebook-github-bot's avatar
facebook-github-bot committed
748
749


Georgia Gkioxari's avatar
Georgia Gkioxari committed
750
751
752
753
754
755
############################################################
#                FoVOrthographic Camera                    #
############################################################


class TestFoVOrthographicProjection(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
756
757
758
    def test_orthographic(self):
        far = 10.0
        near = 1.0
Georgia Gkioxari's avatar
Georgia Gkioxari committed
759
        cameras = FoVOrthographicCameras(znear=near, zfar=far)
facebook-github-bot's avatar
facebook-github-bot committed
760
761
762
763
764
765
766
        P = cameras.get_projection_transform()

        vertices = torch.tensor([1, 2, far], dtype=torch.float32)
        projected_verts = torch.tensor([1, 2, 1], dtype=torch.float32)
        vertices = vertices[None, None, :]
        v1 = P.transform_points(vertices)
        v2 = orthographic_project_naive(vertices)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
767
768
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
769
770
771
772
773

        vertices[..., 2] = near
        projected_verts[2] = 0.0
        v1 = P.transform_points(vertices)
        v2 = orthographic_project_naive(vertices)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
774
775
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
776
777
778
779
780
781
782
783

    def test_orthographic_scaled(self):
        vertices = torch.tensor([1, 2, 0.5], dtype=torch.float32)
        vertices = vertices[None, None, :]
        scale = torch.tensor([[2.0, 0.5, 20]])
        # applying the scale puts the z coordinate at the far clipping plane
        # so the z is mapped to 1.0
        projected_verts = torch.tensor([2, 1, 1], dtype=torch.float32)
Georgia Gkioxari's avatar
Georgia Gkioxari committed
784
        cameras = FoVOrthographicCameras(znear=1.0, zfar=10.0, scale_xyz=scale)
facebook-github-bot's avatar
facebook-github-bot committed
785
786
787
        P = cameras.get_projection_transform()
        v1 = P.transform_points(vertices)
        v2 = orthographic_project_naive(vertices, scale)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
788
789
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v1, projected_verts[None, None])
facebook-github-bot's avatar
facebook-github-bot committed
790
791

    def test_orthographic_kwargs(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
792
        cameras = FoVOrthographicCameras(znear=5.0, zfar=100.0)
facebook-github-bot's avatar
facebook-github-bot committed
793
794
795
796
797
798
        far = 10.0
        P = cameras.get_projection_transform(znear=1.0, zfar=far)
        vertices = torch.tensor([1, 2, far], dtype=torch.float32)
        projected_verts = torch.tensor([1, 2, 1], dtype=torch.float32)
        vertices = vertices[None, None, :]
        v1 = P.transform_points(vertices)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
799
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
800
801
802
803

    def test_orthographic_mixed_inputs_broadcast(self):
        far = torch.tensor([10.0, 20.0])
        near = 1.0
Georgia Gkioxari's avatar
Georgia Gkioxari committed
804
        cameras = FoVOrthographicCameras(znear=near, zfar=far)
facebook-github-bot's avatar
facebook-github-bot committed
805
806
        P = cameras.get_projection_transform()
        vertices = torch.tensor([1.0, 2.0, 10.0], dtype=torch.float32)
Nikhila Ravi's avatar
Nikhila Ravi committed
807
        z2 = 1.0 / (20.0 - 1.0) * 10.0 + -1.0 / (20.0 - 1.0)
facebook-github-bot's avatar
facebook-github-bot committed
808
809
810
811
812
813
        projected_verts = torch.tensor(
            [[1.0, 2.0, 1.0], [1.0, 2.0, z2]], dtype=torch.float32
        )
        vertices = vertices[None, None, :]
        v1 = P.transform_points(vertices)
        v2 = orthographic_project_naive(vertices)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
814
815
        self.assertClose(v1[..., :2], torch.cat([v2, v2])[..., :2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
816
817
818
819
820

    def test_orthographic_mixed_inputs_grad(self):
        far = torch.tensor([10.0])
        near = 1.0
        scale = torch.tensor([[1.0, 1.0, 1.0]], requires_grad=True)
Georgia Gkioxari's avatar
Georgia Gkioxari committed
821
        cameras = FoVOrthographicCameras(znear=near, zfar=far, scale_xyz=scale)
facebook-github-bot's avatar
facebook-github-bot committed
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
        P = cameras.get_projection_transform()
        vertices = torch.tensor([1.0, 2.0, 10.0], dtype=torch.float32)
        vertices_batch = vertices[None, None, :]
        v1 = P.transform_points(vertices_batch)
        v1.sum().backward()
        self.assertTrue(hasattr(scale, "grad"))
        scale_grad = scale.grad.clone()
        grad_scale = torch.tensor(
            [
                [
                    vertices[0] * P._matrix[:, 0, 0],
                    vertices[1] * P._matrix[:, 1, 1],
                    vertices[2] * P._matrix[:, 2, 2],
                ]
            ]
        )
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
838
        self.assertClose(scale_grad, grad_scale)
facebook-github-bot's avatar
facebook-github-bot committed
839
840


Georgia Gkioxari's avatar
Georgia Gkioxari committed
841
842
843
844
845
846
############################################################
#                Orthographic Camera                       #
############################################################


class TestOrthographicProjection(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
847
    def test_orthographic(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
848
        cameras = OrthographicCameras()
facebook-github-bot's avatar
facebook-github-bot committed
849
850
851
852
853
854
855
        P = cameras.get_projection_transform()

        vertices = torch.randn([3, 4, 3], dtype=torch.float32)
        projected_verts = vertices.clone()
        v1 = P.transform_points(vertices)
        v2 = orthographic_project_naive(vertices)

Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
856
857
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v1, projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
858
859
860
861
862

    def test_orthographic_scaled(self):
        focal_length_x = 10.0
        focal_length_y = 15.0

Georgia Gkioxari's avatar
Georgia Gkioxari committed
863
        cameras = OrthographicCameras(focal_length=((focal_length_x, focal_length_y),))
facebook-github-bot's avatar
facebook-github-bot committed
864
865
866
867
868
869
870
871
872
873
874
        P = cameras.get_projection_transform()

        vertices = torch.randn([3, 4, 3], dtype=torch.float32)
        projected_verts = vertices.clone()
        projected_verts[:, :, 0] *= focal_length_x
        projected_verts[:, :, 1] *= focal_length_y
        v1 = P.transform_points(vertices)
        v2 = orthographic_project_naive(
            vertices, scale_xyz=(focal_length_x, focal_length_y, 1.0)
        )
        v3 = cameras.transform_points(vertices)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
875
876
877
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v3[..., :2], v2[..., :2])
        self.assertClose(v1, projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
878
879

    def test_orthographic_kwargs(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
880
        cameras = OrthographicCameras(focal_length=5.0, principal_point=((2.5, 2.5),))
facebook-github-bot's avatar
facebook-github-bot committed
881
882
883
884
885
886
887
888
889
        P = cameras.get_projection_transform(
            focal_length=2.0, principal_point=((2.5, 3.5),)
        )
        vertices = torch.randn([3, 4, 3], dtype=torch.float32)
        projected_verts = vertices.clone()
        projected_verts[:, :, :2] *= 2.0
        projected_verts[:, :, 0] += 2.5
        projected_verts[:, :, 1] += 3.5
        v1 = P.transform_points(vertices)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
890
        self.assertClose(v1, projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
891
892


Georgia Gkioxari's avatar
Georgia Gkioxari committed
893
894
895
896
897
898
############################################################
#                Perspective Camera                        #
############################################################


class TestPerspectiveProjection(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
899
    def test_perspective(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
900
        cameras = PerspectiveCameras()
facebook-github-bot's avatar
facebook-github-bot committed
901
902
903
904
905
        P = cameras.get_projection_transform()

        vertices = torch.randn([3, 4, 3], dtype=torch.float32)
        v1 = P.transform_points(vertices)
        v2 = sfm_perspective_project_naive(vertices)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
906
        self.assertClose(v1, v2)
facebook-github-bot's avatar
facebook-github-bot committed
907
908
909
910
911
912
913

    def test_perspective_scaled(self):
        focal_length_x = 10.0
        focal_length_y = 15.0
        p0x = 15.0
        p0y = 30.0

Georgia Gkioxari's avatar
Georgia Gkioxari committed
914
        cameras = PerspectiveCameras(
facebook-github-bot's avatar
facebook-github-bot committed
915
916
917
918
919
920
921
922
923
924
925
            focal_length=((focal_length_x, focal_length_y),),
            principal_point=((p0x, p0y),),
        )
        P = cameras.get_projection_transform()

        vertices = torch.randn([3, 4, 3], dtype=torch.float32)
        v1 = P.transform_points(vertices)
        v2 = sfm_perspective_project_naive(
            vertices, fx=focal_length_x, fy=focal_length_y, p0x=p0x, p0y=p0y
        )
        v3 = cameras.transform_points(vertices)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
926
927
        self.assertClose(v1, v2)
        self.assertClose(v3[..., :2], v2[..., :2])
facebook-github-bot's avatar
facebook-github-bot committed
928
929

    def test_perspective_kwargs(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
930
        cameras = PerspectiveCameras(focal_length=5.0, principal_point=((2.5, 2.5),))
facebook-github-bot's avatar
facebook-github-bot committed
931
932
933
934
935
        P = cameras.get_projection_transform(
            focal_length=2.0, principal_point=((2.5, 3.5),)
        )
        vertices = torch.randn([3, 4, 3], dtype=torch.float32)
        v1 = P.transform_points(vertices)
936
        v2 = sfm_perspective_project_naive(vertices, fx=2.0, fy=2.0, p0x=2.5, p0y=3.5)
937
        self.assertClose(v1, v2, atol=1e-6)