def test_faces_verts_textures(self): device = torch.device("cuda:0") N, F, R = 2, 2, 8 num_faces = torch.randint(low=1, high=F, size=(N, )) faces_atlas = [ torch.rand(size=(num_faces[i].item(), R, R, 3), device=device) for i in range(N) ] tex = TexturesAtlas(atlas=faces_atlas) # faces_verts naive faces_verts = [] for n in range(N): ff = num_faces[n].item() temp = torch.zeros(ff, 3, 3) for f in range(ff): t0 = faces_atlas[n][f, 0, -1] # for v0, bary = (1, 0) t1 = faces_atlas[n][f, -1, 0] # for v1, bary = (0, 1) t2 = faces_atlas[n][f, 0, 0] # for v2, bary = (0, 0) temp[f, 0] = t0 temp[f, 1] = t1 temp[f, 2] = t2 faces_verts.append(temp) faces_verts = torch.cat(faces_verts, 0) self.assertClose(faces_verts, tex.faces_verts_textures_packed().cpu())
def test_sample_textures_error(self): N = 1 V = 20 F = 10 verts = torch.rand(size=(5, V, 3)) faces = torch.randint(size=(5, F, 3), high=V) meshes = Meshes(verts=verts, faces=faces) # TexturesAtlas have the wrong batch dim tex = TexturesAtlas(atlas=torch.randn(size=(1, F, 4, 4, 3))) with self.assertRaisesRegex(ValueError, "do not match the dimensions"): Meshes(verts=verts, faces=faces, textures=tex) # TexturesAtlas have the wrong number of faces tex = TexturesAtlas(atlas=torch.randn(size=(N, 15, 4, 4, 3))) with self.assertRaisesRegex(ValueError, "do not match the dimensions"): Meshes(verts=verts, faces=faces, textures=tex) meshes = Meshes(verts=verts, faces=faces) meshes.textures = tex # Cannot use the texture attribute set on meshes for sampling # textures if the dimensions don't match with self.assertRaisesRegex(ValueError, "do not match the dimensions"): meshes.sample_textures(None)
def test_textures_atlas_init_fail(self): # Incorrect sized tensors with self.assertRaisesRegex(ValueError, "atlas"): TexturesAtlas(atlas=torch.rand(size=(5, 10, 3))) # Not a list or a tensor with self.assertRaisesRegex(ValueError, "atlas"): TexturesAtlas(atlas=(1, 1, 1))
def test_detach(self): tex = TexturesAtlas(atlas=torch.rand(size=(1, 10, 2, 2, 3), requires_grad=True)) tex.atlas_list() tex_detached = tex.detach() self.assertFalse(tex_detached._atlas_padded.requires_grad) self.assertClose(tex_detached._atlas_padded, tex._atlas_padded) for i in range(tex._N): self.assertFalse(tex_detached._atlas_list[i].requires_grad) self.assertClose(tex._atlas_list[i], tex_detached._atlas_list[i])
def test_clone(self): tex = TexturesAtlas(atlas=torch.rand(size=(1, 10, 2, 2, 3))) tex.atlas_list() tex_cloned = tex.clone() self.assertSeparate(tex._atlas_padded, tex_cloned._atlas_padded) self.assertClose(tex._atlas_padded, tex_cloned._atlas_padded) self.assertSeparate(tex.valid, tex_cloned.valid) self.assertTrue(tex.valid.eq(tex_cloned.valid).all()) for i in range(tex._N): self.assertSeparate(tex._atlas_list[i], tex_cloned._atlas_list[i]) self.assertClose(tex._atlas_list[i], tex_cloned._atlas_list[i])
def test_extend(self): B = 10 mesh = init_mesh(B, 30, 50) F = mesh._F tex_uv = TexturesAtlas(atlas=torch.randn((B, F, 2, 2, 3))) tex_mesh = Meshes(verts=mesh.verts_padded(), faces=mesh.faces_padded(), textures=tex_uv) N = 20 new_mesh = tex_mesh.extend(N) self.assertEqual(len(tex_mesh) * N, len(new_mesh)) tex_init = tex_mesh.textures new_tex = new_mesh.textures for i in range(len(tex_mesh)): for n in range(N): self.assertClose(tex_init.atlas_list()[i], new_tex.atlas_list()[i * N + n]) self.assertClose( tex_init._num_faces_per_mesh[i], new_tex._num_faces_per_mesh[i * N + n], ) self.assertAllSeparate( [tex_init.atlas_padded(), new_tex.atlas_padded()]) with self.assertRaises(ValueError): tex_mesh.extend(N=-1)
def test_textures_atlas_grad(self): N, F, R = 1, 2, 2 verts = torch.randn((4, 3), dtype=torch.float32) faces = torch.tensor([[2, 1, 0], [3, 1, 0]], dtype=torch.int64) faces_atlas = torch.rand(size=(N, F, R, R, 3), requires_grad=True) tex = TexturesAtlas(atlas=faces_atlas) mesh = Meshes(verts=[verts], faces=[faces], textures=tex) pix_to_face = torch.tensor([0, 1], dtype=torch.int64).view(1, 1, 1, 2) barycentric_coords = torch.tensor([[0.5, 0.3, 0.2], [0.3, 0.6, 0.1]], dtype=torch.float32).view( 1, 1, 1, 2, -1) fragments = Fragments( pix_to_face=pix_to_face, bary_coords=barycentric_coords, zbuf=torch.ones_like(pix_to_face), dists=torch.ones_like(pix_to_face), ) texels = mesh.textures.sample_textures(fragments) grad_tex = torch.rand_like(texels) grad_expected = torch.zeros_like(faces_atlas) grad_expected[0, 0, 0, 1, :] = grad_tex[..., 0:1, :] grad_expected[0, 1, 1, 0, :] = grad_tex[..., 1:2, :] texels.backward(grad_tex) self.assertTrue(hasattr(faces_atlas, "grad")) self.assertTrue(torch.allclose(faces_atlas.grad, grad_expected))
def test_sample_texture_atlas(self): N, F, R = 1, 2, 2 verts = torch.randn((4, 3), dtype=torch.float32) faces = torch.tensor([[2, 1, 0], [3, 1, 0]], dtype=torch.int64) faces_atlas = torch.rand(size=(N, F, R, R, 3)) tex = TexturesAtlas(atlas=faces_atlas) mesh = Meshes(verts=[verts], faces=[faces], textures=tex) pix_to_face = torch.tensor([0, 1], dtype=torch.int64).view(1, 1, 1, 2) barycentric_coords = torch.tensor([[0.5, 0.3, 0.2], [0.3, 0.6, 0.1]], dtype=torch.float32).view( 1, 1, 1, 2, -1) expected_vals = torch.tensor([[0.5, 1.0, 0.3], [0.3, 1.0, 0.9]], dtype=torch.float32) expected_vals = torch.zeros((1, 1, 1, 2, 3), dtype=torch.float32) expected_vals[..., 0, :] = faces_atlas[0, 0, 0, 1, ...] expected_vals[..., 1, :] = faces_atlas[0, 1, 1, 0, ...] fragments = Fragments( pix_to_face=pix_to_face, bary_coords=barycentric_coords, zbuf=torch.ones_like(pix_to_face), dists=torch.ones_like(pix_to_face), ) texels = mesh.textures.sample_textures(fragments) self.assertTrue(torch.allclose(texels, expected_vals))
def test_getitem(self): N = 5 V = 20 source = {"atlas": torch.randn(size=(N, 10, 4, 4, 3))} tex = TexturesAtlas(atlas=source["atlas"]) verts = torch.rand(size=(N, V, 3)) faces = torch.randint(size=(N, 10, 3), high=V) meshes = Meshes(verts=verts, faces=faces, textures=tex) tryindex(self, 2, tex, meshes, source) tryindex(self, slice(0, 2, 1), tex, meshes, source) index = torch.tensor([1, 0, 1, 0, 0], dtype=torch.bool) tryindex(self, index, tex, meshes, source) index = torch.tensor([0, 0, 0, 0, 0], dtype=torch.bool) tryindex(self, index, tex, meshes, source) index = torch.tensor([1, 2], dtype=torch.int64) tryindex(self, index, tex, meshes, source) tryindex(self, [2, 4], tex, meshes, source)
def test_padded_to_packed(self): # Case where each face in the mesh has 3 unique uv vertex indices # - i.e. even if a vertex is shared between multiple faces it will # have a unique uv coordinate for each face. R = 2 N = 20 num_faces_per_mesh = torch.randint(size=(N, ), low=0, high=30) atlas_list = [torch.rand(f, R, R, 3) for f in num_faces_per_mesh] tex = TexturesAtlas(atlas=atlas_list) # This is set inside Meshes when textures is passed as an input. # Here we set _num_faces_per_mesh explicity. tex1 = tex.clone() tex1._num_faces_per_mesh = num_faces_per_mesh.tolist() atlas_packed = tex1.atlas_packed() atlas_list_new = tex1.atlas_list() atlas_padded = tex1.atlas_padded() for f1, f2 in zip(atlas_list_new, atlas_list): self.assertTrue((f1 == f2).all().item()) sum_F = num_faces_per_mesh.sum() max_F = num_faces_per_mesh.max().item() self.assertTrue(atlas_packed.shape == (sum_F, R, R, 3)) self.assertTrue(atlas_padded.shape == (N, max_F, R, R, 3)) # Case where num_faces_per_mesh is not set and textures # are initialized with a padded tensor. atlas_list_padded = _list_to_padded_wrapper(atlas_list) tex2 = TexturesAtlas(atlas=atlas_list_padded) atlas_packed = tex2.atlas_packed() atlas_list_new = tex2.atlas_list() # Packed is just flattened padded as num_faces_per_mesh # has not been provided. self.assertTrue(atlas_packed.shape == (N * max_F, R, R, 3)) for i, (f1, f2) in enumerate(zip(atlas_list_new, atlas_list)): n = num_faces_per_mesh[i] self.assertTrue((f1[:n] == f2).all().item())
def test_clone(self): tex = TexturesAtlas(atlas=torch.rand(size=(1, 10, 2, 2, 3))) tex_cloned = tex.clone() self.assertSeparate(tex._atlas_padded, tex_cloned._atlas_padded) self.assertSeparate(tex.valid, tex_cloned.valid)