test_cameras.py 37.7 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
# 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
David Novotny's avatar
David Novotny committed
29
import typing
facebook-github-bot's avatar
facebook-github-bot committed
30
31
import unittest

32
33
34
import numpy as np
import torch
from common_testing import TestCaseMixin
Georgia Gkioxari's avatar
Georgia Gkioxari committed
35
36
37
38
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
39
from pytorch3d.renderer.cameras import (
40
    CamerasBase,
Georgia Gkioxari's avatar
Georgia Gkioxari committed
41
42
43
44
    FoVOrthographicCameras,
    FoVPerspectiveCameras,
    OrthographicCameras,
    PerspectiveCameras,
facebook-github-bot's avatar
facebook-github-bot committed
45
46
47
    camera_position_from_spherical_angles,
    get_world_to_view_transform,
    look_at_rotation,
48
    look_at_view_transform,
facebook-github-bot's avatar
facebook-github-bot committed
49
50
)
from pytorch3d.transforms import Transform3d
David Novotny's avatar
David Novotny committed
51
from pytorch3d.transforms.rotation_conversions import random_rotations
facebook-github-bot's avatar
facebook-github-bot committed
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
67
    halfFov = torch.tensor((fov / 2) / 180 * np.pi, dtype=torch.float32, device=device)
facebook-github-bot's avatar
facebook-github-bot committed
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
    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]
91
92
    x = (points[:, :, 0] * fx) / z + p0x
    y = (points[:, :, 1] * fy) / z + p0y
facebook-github-bot's avatar
facebook-github-bot committed
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
    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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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)


David Novotny's avatar
David Novotny committed
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def init_random_cameras(
    cam_type: typing.Type[CamerasBase], batch_size: int, random_z: bool = False
):
    cam_params = {}
    T = torch.randn(batch_size, 3) * 0.03
    if not random_z:
        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
            cam_params["bottom"] = -(torch.rand(batch_size)) * 0.2 - 0.9
            cam_params["left"] = -(torch.rand(batch_size)) * 0.2 - 0.9
            cam_params["right"] = torch.rand(batch_size) * 0.2 + 0.9
    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,
    ):
        cam_params["focal_length"] = torch.rand(batch_size) * 10 + 0.1
        cam_params["principal_point"] = torch.randn((batch_size, 2))

    else:
        raise ValueError(str(cam_type))
    return cam_type(**cam_params)


Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
182
class TestCameraHelpers(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
183
184
185
186
187
    def setUp(self) -> None:
        super().setUp()
        torch.manual_seed(42)
        np.random.seed(42)

188
189
190
191
    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
192
        eye = ((0.0, 1.0, 1.0),)
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
        # 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))

217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
    def test_look_at_view_transform_non_default_at_position(self):
        dist = 1.0
        elev = 0.0
        azim = 0.0
        at = ((1, 1, 1),)
        # Using passed values for dist, elev, azim, at
        R, t = look_at_view_transform(dist, elev, azim, at=at)
        # Using default dist=1.0, elev=0.0, azim=0.0
        R_default, t_default = look_at_view_transform()
        # test default = passed = expected
        # R must be the same, t must be translated by (1,-1,1) with respect to t_default
        t_trans = torch.tensor([1, -1, 1], dtype=torch.float32).view(1, 3)
        self.assertTrue(torch.allclose(R, R_default, atol=2e-7))
        self.assertTrue(torch.allclose(t, t_default + t_trans, atol=2e-7))

facebook-github-bot's avatar
facebook-github-bot committed
232
233
234
235
    def test_camera_position_from_angles_python_scalar(self):
        dist = 2.7
        elev = 90.0
        azim = 0.0
236
237
238
        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
239
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
240
        self.assertClose(position, expected_position, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
241
242
243
244
245
246
247
248
249
250

    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
251
        self.assertClose(position, expected_position, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
252
253
254
255
256

    def test_camera_position_from_angles_torch_scalars(self):
        dist = torch.tensor(2.7)
        elev = torch.tensor(0.0)
        azim = torch.tensor(90.0)
257
258
259
        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
260
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
261
        self.assertClose(position, expected_position, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
262
263
264
265
266

    def test_camera_position_from_angles_mixed_scalars(self):
        dist = 2.7
        elev = torch.tensor(0.0)
        azim = 90.0
267
268
269
        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
270
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
271
        self.assertClose(position, expected_position, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287

    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)
288
            + torch.cos(elev) * torch.cos(azim)
facebook-github-bot's avatar
facebook-github-bot committed
289
290
        )
        grad_elev = (
Nikhila Ravi's avatar
Nikhila Ravi committed
291
            -(torch.sin(elev)) * torch.sin(azim)
facebook-github-bot's avatar
facebook-github-bot committed
292
            + torch.cos(elev)
293
            - torch.sin(elev) * torch.cos(azim)
facebook-github-bot's avatar
facebook-github-bot committed
294
295
        )
        grad_elev = dist * (math.pi / 180.0) * grad_elev
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
296
297
        self.assertClose(elev_grad, grad_elev)
        self.assertClose(dist_grad, grad_dist)
facebook-github-bot's avatar
facebook-github-bot committed
298
299
300
301
302
303
304
305
306

    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
307
        self.assertClose(position, expected_position, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
308
309
310
311
312
313

    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(
314
            [[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
315
316
        )
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
317
        self.assertClose(position, expected_position, atol=3e-7)
facebook-github-bot's avatar
facebook-github-bot committed
318
319
320
321
322
323

    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(
324
            [[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
325
326
        )
        position = camera_position_from_spherical_angles(dist, elev, azim)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
327
        self.assertClose(position, expected_position, atol=3e-7)
facebook-github-bot's avatar
facebook-github-bot committed
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344

    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)
345
            + torch.cos(elev) * torch.cos(azim)
facebook-github-bot's avatar
facebook-github-bot committed
346
347
        )
        grad_elev = (
Nikhila Ravi's avatar
Nikhila Ravi committed
348
            -(torch.sin(elev)) * torch.sin(azim)
facebook-github-bot's avatar
facebook-github-bot committed
349
            + torch.cos(elev)
350
            - torch.sin(elev) * torch.cos(azim)
facebook-github-bot's avatar
facebook-github-bot committed
351
352
        )
        grad_elev = (dist * (math.pi / 180.0) * grad_elev).sum()
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
353
354
        self.assertClose(elev_grad, grad_elev)
        self.assertClose(dist_grad, torch.full([3], grad_dist))
facebook-github-bot's avatar
facebook-github-bot committed
355
356
357
358
359
360
361
362
363
364
365
366

    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
367
        self.assertClose(rot_mat, torch.eye(3)[None], atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393

    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
394
        self.assertClose(rot_mats, rot_mats_expected, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418

    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
419
        self.assertClose(rot_mats, rot_mats_expected, atol=2e-7)
facebook-github-bot's avatar
facebook-github-bot committed
420
421
422
423
424
425

    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
426
427
        self.assertClose(
            camera_position.grad, torch.zeros_like(camera_position), atol=2e-7
facebook-github-bot's avatar
facebook-github-bot committed
428
429
430
431
432
433
434
435
        )

    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))

436
437

class TestCamerasCommon(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
438
439
440
441
442
443
444
445
446
    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
447
448
449
450
            FoVOrthographicCameras,
            FoVPerspectiveCameras,
            OrthographicCameras,
            PerspectiveCameras,
facebook-github-bot's avatar
facebook-github-bot committed
451
452
453
        ):
            cam = cam_type(R=R, T=T)
            RT_class = cam.get_world_to_view_transform()
454
            self.assertTrue(torch.allclose(RT.get_matrix(), RT_class.get_matrix()))
facebook-github-bot's avatar
facebook-github-bot committed
455
456
457
458
459

        self.assertTrue(isinstance(RT, Transform3d))

    def test_get_camera_center(self, batch_size=10):
        T = torch.randn(batch_size, 3)
David Novotny's avatar
David Novotny committed
460
        R = random_rotations(batch_size)
facebook-github-bot's avatar
facebook-github-bot committed
461
462
463
464
465
        for cam_type in (
            OpenGLPerspectiveCameras,
            OpenGLOrthographicCameras,
            SfMOrthographicCameras,
            SfMPerspectiveCameras,
Georgia Gkioxari's avatar
Georgia Gkioxari committed
466
467
468
469
            FoVOrthographicCameras,
            FoVPerspectiveCameras,
            OrthographicCameras,
            PerspectiveCameras,
facebook-github-bot's avatar
facebook-github-bot committed
470
471
472
473
474
475
        ):
            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))

Georgia Gkioxari's avatar
Georgia Gkioxari committed
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
    @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)

499
500
501
502
503
504
505
506
507
508
509
    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
510
511
512
513
            FoVOrthographicCameras,
            FoVPerspectiveCameras,
            OrthographicCameras,
            PerspectiveCameras,
514
515
        ):
            # init the cameras
David Novotny's avatar
David Novotny committed
516
            cameras = init_random_cameras(cam_type, batch_size)
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
            # 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
535
                # if we have FoV (= OpenGL) cameras
536
                # test for scaled_depth_input=True/False
Georgia Gkioxari's avatar
Georgia Gkioxari committed
537
538
539
540
541
542
                if cam_type in (
                    OpenGLPerspectiveCameras,
                    OpenGLOrthographicCameras,
                    FoVPerspectiveCameras,
                    FoVOrthographicCameras,
                ):
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
                    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
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
    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
David Novotny's avatar
David Novotny committed
580
            cameras = init_random_cameras(cam_type, batch_size)
Georgia Gkioxari's avatar
Georgia Gkioxari committed
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
            # 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)

612
613
614
615
616
617
618
619
620
    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
621
622
623
624
            FoVOrthographicCameras,
            FoVPerspectiveCameras,
            OrthographicCameras,
            PerspectiveCameras,
625
        ):
David Novotny's avatar
David Novotny committed
626
            cameras = init_random_cameras(cam_type, batch_size)
627
628
629
630
631
632
633
634
635
636
637
638
            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
639

Georgia Gkioxari's avatar
Georgia Gkioxari committed
640
641
642
643
644
645
############################################################
#                FoVPerspective Camera                     #
############################################################


class TestFoVPerspectiveProjection(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
646
647
648
    def test_perspective(self):
        far = 10.0
        near = 1.0
Georgia Gkioxari's avatar
Georgia Gkioxari committed
649
        cameras = FoVPerspectiveCameras(znear=near, zfar=far, fov=60.0)
facebook-github-bot's avatar
facebook-github-bot committed
650
651
652
653
654
655
656
657
658
        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
659
660
661
        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
662
663
664
665
666
667
668
669

        # 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
670
671
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
672
673

    def test_perspective_kwargs(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
674
        cameras = FoVPerspectiveCameras(znear=5.0, zfar=100.0, fov=0.0)
facebook-github-bot's avatar
facebook-github-bot committed
675
676
677
678
679
680
681
682
683
        # 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
684
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
685
686
687
688
689

    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
690
        cameras = FoVPerspectiveCameras(znear=near, zfar=far, fov=fov)
facebook-github-bot's avatar
facebook-github-bot committed
691
692
693
        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
694
        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
695
696
697
698
699
700
701
702
703
704
        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
705
706
        self.assertClose(v1[..., :2], torch.cat([v2, v2])[..., :2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
707
708
709
710
711

    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
712
        cameras = FoVPerspectiveCameras(znear=near, zfar=far, fov=fov)
facebook-github-bot's avatar
facebook-github-bot committed
713
714
715
716
717
718
719
720
721
722
723
        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
724
        self.assertClose(fov_grad, grad_fov)
facebook-github-bot's avatar
facebook-github-bot committed
725
726
727

    def test_camera_class_init(self):
        device = torch.device("cuda:0")
Georgia Gkioxari's avatar
Georgia Gkioxari committed
728
        cam = FoVPerspectiveCameras(znear=10.0, zfar=(100.0, 200.0))
facebook-github-bot's avatar
facebook-github-bot committed
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746

        # 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
747
        cam = FoVPerspectiveCameras()
facebook-github-bot's avatar
facebook-github-bot committed
748
749
750
751
        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
752
753
        self.assertClose(cam.R, R)
        self.assertClose(cam.T, T)
facebook-github-bot's avatar
facebook-github-bot committed
754
755
756
757
758

    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
759
        cam = FoVPerspectiveCameras(znear=1.0, zfar=far, fov=60.0)
facebook-github-bot's avatar
facebook-github-bot committed
760
761
762
763
764
765
766
        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
767
        self.assertClose(new_points, projected_points)
facebook-github-bot's avatar
facebook-github-bot committed
768
769


Georgia Gkioxari's avatar
Georgia Gkioxari committed
770
771
772
773
774
775
############################################################
#                FoVOrthographic Camera                    #
############################################################


class TestFoVOrthographicProjection(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
776
777
778
    def test_orthographic(self):
        far = 10.0
        near = 1.0
Georgia Gkioxari's avatar
Georgia Gkioxari committed
779
        cameras = FoVOrthographicCameras(znear=near, zfar=far)
facebook-github-bot's avatar
facebook-github-bot committed
780
781
782
783
784
785
786
        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
787
788
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
789
790
791
792
793

        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
794
795
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
796
797
798
799
800
801
802
803

    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
804
        cameras = FoVOrthographicCameras(znear=1.0, zfar=10.0, scale_xyz=scale)
facebook-github-bot's avatar
facebook-github-bot committed
805
806
807
        P = cameras.get_projection_transform()
        v1 = P.transform_points(vertices)
        v2 = orthographic_project_naive(vertices, scale)
Jeremy Reizenstein's avatar
Jeremy Reizenstein committed
808
809
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v1, projected_verts[None, None])
facebook-github-bot's avatar
facebook-github-bot committed
810
811

    def test_orthographic_kwargs(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
812
        cameras = FoVOrthographicCameras(znear=5.0, zfar=100.0)
facebook-github-bot's avatar
facebook-github-bot committed
813
814
815
816
817
818
        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
819
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
820
821
822
823

    def test_orthographic_mixed_inputs_broadcast(self):
        far = torch.tensor([10.0, 20.0])
        near = 1.0
Georgia Gkioxari's avatar
Georgia Gkioxari committed
824
        cameras = FoVOrthographicCameras(znear=near, zfar=far)
facebook-github-bot's avatar
facebook-github-bot committed
825
826
        P = cameras.get_projection_transform()
        vertices = torch.tensor([1.0, 2.0, 10.0], dtype=torch.float32)
Nikhila Ravi's avatar
Nikhila Ravi committed
827
        z2 = 1.0 / (20.0 - 1.0) * 10.0 + -1.0 / (20.0 - 1.0)
facebook-github-bot's avatar
facebook-github-bot committed
828
829
830
831
832
833
        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
834
835
        self.assertClose(v1[..., :2], torch.cat([v2, v2])[..., :2])
        self.assertClose(v1.squeeze(), projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
836
837
838
839
840

    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
841
        cameras = FoVOrthographicCameras(znear=near, zfar=far, scale_xyz=scale)
facebook-github-bot's avatar
facebook-github-bot committed
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
        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
858
        self.assertClose(scale_grad, grad_scale)
facebook-github-bot's avatar
facebook-github-bot committed
859
860


Georgia Gkioxari's avatar
Georgia Gkioxari committed
861
862
863
864
865
866
############################################################
#                Orthographic Camera                       #
############################################################


class TestOrthographicProjection(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
867
    def test_orthographic(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
868
        cameras = OrthographicCameras()
facebook-github-bot's avatar
facebook-github-bot committed
869
870
871
872
873
874
875
        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
876
877
        self.assertClose(v1[..., :2], v2[..., :2])
        self.assertClose(v1, projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
878
879
880
881
882

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

Georgia Gkioxari's avatar
Georgia Gkioxari committed
883
        cameras = OrthographicCameras(focal_length=((focal_length_x, focal_length_y),))
facebook-github-bot's avatar
facebook-github-bot committed
884
885
886
887
888
889
890
891
892
893
894
        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
895
896
897
        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
898
899

    def test_orthographic_kwargs(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
900
        cameras = OrthographicCameras(focal_length=5.0, principal_point=((2.5, 2.5),))
facebook-github-bot's avatar
facebook-github-bot committed
901
902
903
904
905
906
907
908
909
        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
910
        self.assertClose(v1, projected_verts)
facebook-github-bot's avatar
facebook-github-bot committed
911
912


Georgia Gkioxari's avatar
Georgia Gkioxari committed
913
914
915
916
917
918
############################################################
#                Perspective Camera                        #
############################################################


class TestPerspectiveProjection(TestCaseMixin, unittest.TestCase):
facebook-github-bot's avatar
facebook-github-bot committed
919
    def test_perspective(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
920
        cameras = PerspectiveCameras()
facebook-github-bot's avatar
facebook-github-bot committed
921
922
923
924
925
        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
926
        self.assertClose(v1, v2)
facebook-github-bot's avatar
facebook-github-bot committed
927
928
929
930
931
932
933

    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
934
        cameras = PerspectiveCameras(
facebook-github-bot's avatar
facebook-github-bot committed
935
936
937
938
939
940
941
942
943
944
945
            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
946
947
        self.assertClose(v1, v2)
        self.assertClose(v3[..., :2], v2[..., :2])
facebook-github-bot's avatar
facebook-github-bot committed
948
949

    def test_perspective_kwargs(self):
Georgia Gkioxari's avatar
Georgia Gkioxari committed
950
        cameras = PerspectiveCameras(focal_length=5.0, principal_point=((2.5, 2.5),))
facebook-github-bot's avatar
facebook-github-bot committed
951
952
953
954
955
        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)
956
        v2 = sfm_perspective_project_naive(vertices, fx=2.0, fy=2.0, p0x=2.5, p0y=3.5)
957
        self.assertClose(v1, v2, atol=1e-6)