# Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. import unittest import torch from pytorch3d.ops import cubify from pytorch3d.renderer.mesh.textures import TexturesAtlas from .common_testing import TestCaseMixin class TestCubify(TestCaseMixin, unittest.TestCase): def test_allempty(self): N, V = 32, 14 device = torch.device("cuda:0") voxels = torch.zeros((N, V, V, V), dtype=torch.float32, device=device) meshes = cubify(voxels, 0.5) self.assertTrue(meshes.isempty()) def test_cubify(self): N, V = 4, 2 device = torch.device("cuda:0") voxels = torch.zeros((N, V, V, V), dtype=torch.float32, device=device) # 1st example: (top left corner, znear) is on voxels[0, 0, 0, 0] = 1.0 # 2nd example: all are on voxels[1] = 1.0 # 3rd example: empty # 4th example voxels[3, :, :, 1] = 1.0 voxels[3, 1, 1, 0] = 1.0 # compute cubify meshes = cubify(voxels, 0.5) # 1st-check verts, faces = meshes.get_mesh_verts_faces(0) self.assertClose(faces.max().cpu(), torch.tensor(verts.size(0) - 1)) self.assertClose( verts, torch.tensor( [ [-1.0, -1.0, -1.0], [-1.0, -1.0, 1.0], [1.0, -1.0, -1.0], [1.0, -1.0, 1.0], [-1.0, 1.0, -1.0], [-1.0, 1.0, 1.0], [1.0, 1.0, -1.0], [1.0, 1.0, 1.0], ], dtype=torch.float32, device=device, ), ) self.assertClose( faces, torch.tensor( [ [0, 1, 4], [1, 5, 4], [4, 5, 6], [5, 7, 6], [0, 4, 6], [0, 6, 2], [0, 3, 1], [0, 2, 3], [6, 7, 3], [6, 3, 2], [1, 7, 5], [1, 3, 7], ], dtype=torch.int64, device=device, ), ) # 2nd-check verts, faces = meshes.get_mesh_verts_faces(1) self.assertClose(faces.max().cpu(), torch.tensor(verts.size(0) - 1)) self.assertClose( verts, torch.tensor( [ [-1.0, -1.0, -1.0], [-1.0, -1.0, 1.0], [-1.0, -1.0, 3.0], [1.0, -1.0, -1.0], [1.0, -1.0, 1.0], [1.0, -1.0, 3.0], [3.0, -1.0, -1.0], [3.0, -1.0, 1.0], [3.0, -1.0, 3.0], [-1.0, 1.0, -1.0], [-1.0, 1.0, 1.0], [-1.0, 1.0, 3.0], [1.0, 1.0, -1.0], [1.0, 1.0, 3.0], [3.0, 1.0, -1.0], [3.0, 1.0, 1.0], [3.0, 1.0, 3.0], [-1.0, 3.0, -1.0], [-1.0, 3.0, 1.0], [-1.0, 3.0, 3.0], [1.0, 3.0, -1.0], [1.0, 3.0, 1.0], [1.0, 3.0, 3.0], [3.0, 3.0, -1.0], [3.0, 3.0, 1.0], [3.0, 3.0, 3.0], ], dtype=torch.float32, device=device, ), ) self.assertClose( faces, torch.tensor( [ [0, 1, 9], [1, 10, 9], [0, 9, 12], [0, 12, 3], [0, 4, 1], [0, 3, 4], [1, 2, 10], [2, 11, 10], [1, 5, 2], [1, 4, 5], [2, 13, 11], [2, 5, 13], [3, 12, 14], [3, 14, 6], [3, 7, 4], [3, 6, 7], [14, 15, 7], [14, 7, 6], [4, 8, 5], [4, 7, 8], [15, 16, 8], [15, 8, 7], [5, 16, 13], [5, 8, 16], [9, 10, 17], [10, 18, 17], [17, 18, 20], [18, 21, 20], [9, 17, 20], [9, 20, 12], [10, 11, 18], [11, 19, 18], [18, 19, 21], [19, 22, 21], [11, 22, 19], [11, 13, 22], [20, 21, 23], [21, 24, 23], [12, 20, 23], [12, 23, 14], [23, 24, 15], [23, 15, 14], [21, 22, 24], [22, 25, 24], [24, 25, 16], [24, 16, 15], [13, 25, 22], [13, 16, 25], ], dtype=torch.int64, device=device, ), ) # 3rd-check verts, faces = meshes.get_mesh_verts_faces(2) self.assertTrue(verts.size(0) == 0) self.assertTrue(faces.size(0) == 0) # 4th-check verts, faces = meshes.get_mesh_verts_faces(3) self.assertClose( verts, torch.tensor( [ [1.0, -1.0, -1.0], [1.0, -1.0, 1.0], [1.0, -1.0, 3.0], [3.0, -1.0, -1.0], [3.0, -1.0, 1.0], [3.0, -1.0, 3.0], [-1.0, 1.0, 1.0], [-1.0, 1.0, 3.0], [1.0, 1.0, -1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 3.0], [3.0, 1.0, -1.0], [3.0, 1.0, 1.0], [3.0, 1.0, 3.0], [-1.0, 3.0, 1.0], [-1.0, 3.0, 3.0], [1.0, 3.0, -1.0], [1.0, 3.0, 1.0], [1.0, 3.0, 3.0], [3.0, 3.0, -1.0], [3.0, 3.0, 1.0], [3.0, 3.0, 3.0], ], dtype=torch.float32, device=device, ), ) self.assertClose( faces, torch.tensor( [ [0, 1, 8], [1, 9, 8], [0, 8, 11], [0, 11, 3], [0, 4, 1], [0, 3, 4], [11, 12, 4], [11, 4, 3], [1, 2, 9], [2, 10, 9], [1, 5, 2], [1, 4, 5], [12, 13, 5], [12, 5, 4], [2, 13, 10], [2, 5, 13], [6, 7, 14], [7, 15, 14], [14, 15, 17], [15, 18, 17], [6, 14, 17], [6, 17, 9], [6, 10, 7], [6, 9, 10], [7, 18, 15], [7, 10, 18], [8, 9, 16], [9, 17, 16], [16, 17, 19], [17, 20, 19], [8, 16, 19], [8, 19, 11], [19, 20, 12], [19, 12, 11], [17, 18, 20], [18, 21, 20], [20, 21, 13], [20, 13, 12], [10, 21, 18], [10, 13, 21], ], dtype=torch.int64, device=device, ), ) def test_align(self): N, V = 1, 2 device = torch.device("cuda:0") voxels = torch.ones((N, V, V, V), dtype=torch.float32, device=device) # topleft align mesh = cubify(voxels, 0.5) verts, faces = mesh.get_mesh_verts_faces(0) self.assertClose(verts.min(), torch.tensor(-1.0, device=device)) self.assertClose(verts.max(), torch.tensor(3.0, device=device)) # corner align mesh = cubify(voxels, 0.5, align="corner") verts, faces = mesh.get_mesh_verts_faces(0) self.assertClose(verts.min(), torch.tensor(-1.0, device=device)) self.assertClose(verts.max(), torch.tensor(1.0, device=device)) # center align mesh = cubify(voxels, 0.5, align="center") verts, faces = mesh.get_mesh_verts_faces(0) self.assertClose(verts.min(), torch.tensor(-2.0, device=device)) self.assertClose(verts.max(), torch.tensor(2.0, device=device)) # invalid align with self.assertRaisesRegex(ValueError, "Align mode must be one of"): cubify(voxels, 0.5, align="") # invalid align with self.assertRaisesRegex(ValueError, "Align mode must be one of"): cubify(voxels, 0.5, align="topright") # inside occupancy, similar to GH#185 use case N, V = 1, 4 voxels = torch.zeros((N, V, V, V), dtype=torch.float32, device=device) voxels[0, : V // 2, : V // 2, : V // 2] = 1.0 mesh = cubify(voxels, 0.5, align="corner") verts, faces = mesh.get_mesh_verts_faces(0) self.assertClose(verts.min(), torch.tensor(-1.0, device=device)) self.assertClose(verts.max(), torch.tensor(0.0, device=device)) @staticmethod def cubify_with_init(batch_size: int, V: int): device = torch.device("cuda:0") voxels = torch.rand((batch_size, V, V, V), dtype=torch.float32, device=device) torch.cuda.synchronize() def convert(): cubify(voxels, 0.5) torch.cuda.synchronize() return convert def test_cubify_with_feats(self): N, V = 3, 2 device = torch.device("cuda:0") voxels = torch.zeros((N, V, V, V), dtype=torch.float32, device=device) feats = torch.zeros((N, 3, V, V, V), dtype=torch.float32, device=device) # fill the feats with red color feats[:, 0, :, :, :] = 255 # 1st example: (top left corner, znear) is on voxels[0, 0, 0, 0] = 1.0 # the color is set to green feats[0, :, 0, 0, 0] = torch.Tensor([0, 255, 0]) # 2nd example: all are on voxels[1] = 1.0 # 3rd example voxels[2, :, :, 1] = 1.0 voxels[2, 1, 1, 0] = 1.0 # the color is set to yellow and blue respectively feats[2, 1, :, :, 1] = 255 feats[2, :, 1, 1, 0] = torch.Tensor([0, 0, 255]) meshes = cubify(voxels, 0.5, feats=feats, align="center") textures = meshes.textures self.assertTrue(textures is not None) self.assertTrue(isinstance(textures, TexturesAtlas)) faces_textures = textures.faces_verts_textures_packed() red = faces_textures.new_tensor([255.0, 0.0, 0.0]) green = faces_textures.new_tensor([0.0, 255.0, 0.0]) blue = faces_textures.new_tensor([0.0, 0.0, 255.0]) yellow = faces_textures.new_tensor([255.0, 255.0, 0.0]) self.assertEqual(faces_textures.shape, (100, 3, 3)) faces_textures_ = faces_textures.flatten(end_dim=1) self.assertClose(faces_textures_[:36], green.expand(36, -1)) self.assertClose(faces_textures_[36:180], red.expand(144, -1)) self.assertClose(faces_textures_[180:228], yellow.expand(48, -1)) self.assertClose(faces_textures_[228:258], blue.expand(30, -1)) self.assertClose(faces_textures_[258:300], yellow.expand(42, -1))