def test_centers_for_image(self): maps = torch.rand(size=(1, 257, 129, 3)) verts_uvs = torch.FloatTensor([[[0.25, 0.125], [0.5, 0.625], [0.5, 0.5]]]) faces_uvs = torch.zeros(size=(1, 0, 3), dtype=torch.int64) tex = TexturesUV(maps=maps, faces_uvs=faces_uvs, verts_uvs=verts_uvs) expected = torch.FloatTensor([[32, 224], [64, 96], [64, 128]]) self.assertClose(tex.centers_for_image(0), expected)
def test_clone(self): tex = TexturesUV( maps=torch.ones((5, 16, 16, 3)), faces_uvs=torch.rand(size=(5, 10, 3)), verts_uvs=torch.rand(size=(5, 15, 2)), ) tex.faces_uvs_list() tex.verts_uvs_list() tex_cloned = tex.clone() self.assertSeparate(tex._faces_uvs_padded, tex_cloned._faces_uvs_padded) self.assertClose(tex._faces_uvs_padded, tex_cloned._faces_uvs_padded) self.assertSeparate(tex._verts_uvs_padded, tex_cloned._verts_uvs_padded) self.assertClose(tex._verts_uvs_padded, tex_cloned._verts_uvs_padded) self.assertSeparate(tex._maps_padded, tex_cloned._maps_padded) self.assertClose(tex._maps_padded, tex_cloned._maps_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._faces_uvs_list[i], tex_cloned._faces_uvs_list[i]) self.assertClose(tex._faces_uvs_list[i], tex_cloned._faces_uvs_list[i]) self.assertSeparate(tex._verts_uvs_list[i], tex_cloned._verts_uvs_list[i]) self.assertClose(tex._verts_uvs_list[i], tex_cloned._verts_uvs_list[i]) # tex._maps_list is not use anywhere so it's not stored. We call it explicitly self.assertSeparate(tex.maps_list()[i], tex_cloned.maps_list()[i]) self.assertClose(tex.maps_list()[i], tex_cloned.maps_list()[i])
def test_to(self): tex = TexturesUV( maps=torch.ones((5, 16, 16, 3)), faces_uvs=torch.randint(size=(5, 10, 3), high=15), verts_uvs=torch.rand(size=(5, 15, 2)), ) device = torch.device("cuda:0") tex = tex.to(device) self.assertEqual(tex._faces_uvs_padded.device, device) self.assertEqual(tex._verts_uvs_padded.device, device) self.assertEqual(tex._maps_padded.device, device)
def test_clone(self): tex = TexturesUV( maps=torch.ones((5, 16, 16, 3)), faces_uvs=torch.rand(size=(5, 10, 3)), verts_uvs=torch.rand(size=(5, 15, 2)), ) tex_cloned = tex.clone() self.assertSeparate(tex._faces_uvs_padded, tex_cloned._faces_uvs_padded) self.assertSeparate(tex._verts_uvs_padded, tex_cloned._verts_uvs_padded) self.assertSeparate(tex._maps_padded, tex_cloned._maps_padded) self.assertSeparate(tex.valid, tex_cloned.valid)
def test_detach(self): tex = TexturesUV( maps=torch.ones((5, 16, 16, 3), requires_grad=True), faces_uvs=torch.rand(size=(5, 10, 3)), verts_uvs=torch.rand(size=(5, 15, 2)), ) tex.faces_uvs_list() tex.verts_uvs_list() tex_detached = tex.detach() self.assertFalse(tex_detached._maps_padded.requires_grad) self.assertClose(tex._maps_padded, tex_detached._maps_padded) self.assertFalse(tex_detached._verts_uvs_padded.requires_grad) self.assertClose(tex._verts_uvs_padded, tex_detached._verts_uvs_padded) self.assertFalse(tex_detached._faces_uvs_padded.requires_grad) self.assertClose(tex._faces_uvs_padded, tex_detached._faces_uvs_padded) for i in range(tex._N): self.assertFalse(tex_detached._verts_uvs_list[i].requires_grad) self.assertClose(tex._verts_uvs_list[i], tex_detached._verts_uvs_list[i]) self.assertFalse(tex_detached._faces_uvs_list[i].requires_grad) self.assertClose(tex._faces_uvs_list[i], tex_detached._faces_uvs_list[i]) # tex._maps_list is not use anywhere so it's not stored. We call it explicitly self.assertFalse(tex_detached.maps_list()[i].requires_grad) self.assertClose(tex.maps_list()[i], tex_detached.maps_list()[i])
def test_getitem(self): N = 5 V = 20 F = 10 source = { "maps": torch.rand(size=(N, 1, 1, 3)), "faces_uvs": torch.randint(size=(N, F, 3), high=V), "verts_uvs": torch.randn(size=(N, V, 2)), } tex = TexturesUV( maps=source["maps"], faces_uvs=source["faces_uvs"], verts_uvs=source["verts_uvs"], ) verts = torch.rand(size=(N, V, 3)) faces = torch.randint(size=(N, F, 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_extend(self): B = 5 mesh = init_mesh(B, 30, 50) V = mesh._V num_faces = mesh.num_faces_per_mesh() num_verts = mesh.num_verts_per_mesh() faces_uvs_list = [ torch.randint(size=(f, 3), low=0, high=V) for f in num_faces ] verts_uvs_list = [torch.rand(v, 2) for v in num_verts] tex_uv = TexturesUV( maps=torch.ones((B, 16, 16, 3)), faces_uvs=faces_uvs_list, verts_uvs=verts_uvs_list, ) tex_mesh = Meshes(verts=mesh.verts_list(), faces=mesh.faces_list(), textures=tex_uv) N = 2 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 new_tex_num_verts = new_mesh.num_verts_per_mesh() for i in range(len(tex_mesh)): for n in range(N): tex_nv = new_tex_num_verts[i * N + n] self.assertClose( # The original textures were initialized using # verts uvs list tex_init.verts_uvs_list()[i], # In the new textures, the verts_uvs are initialized # from padded. The verts per mesh are not used to # convert from padded to list. See TexturesUV for an # explanation. new_tex.verts_uvs_list()[i * N + n][:tex_nv, ...], ) self.assertClose(tex_init.faces_uvs_list()[i], new_tex.faces_uvs_list()[i * N + n]) self.assertClose(tex_init.maps_padded()[i, ...], new_tex.maps_padded()[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.faces_uvs_padded(), new_tex.faces_uvs_padded(), tex_init.verts_uvs_padded(), new_tex.verts_uvs_padded(), tex_init.maps_padded(), new_tex.maps_padded(), ]) with self.assertRaises(ValueError): tex_mesh.extend(N=-1)
def test_extend(self): B = 5 mesh = TestMeshes.init_mesh(B, 30, 50) V = mesh._V num_faces = mesh.num_faces_per_mesh() num_verts = mesh.num_verts_per_mesh() faces_uvs_list = [ torch.randint(size=(f, 3), low=0, high=V) for f in num_faces ] verts_uvs_list = [torch.rand(v, 2) for v in num_verts] tex_uv = TexturesUV( maps=torch.ones((B, 16, 16, 3)), faces_uvs=faces_uvs_list, verts_uvs=verts_uvs_list, ) tex_mesh = Meshes(verts=mesh.verts_list(), faces=mesh.faces_list(), textures=tex_uv) N = 2 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.verts_uvs_list()[i], new_tex.verts_uvs_list()[i * N + n]) self.assertClose(tex_init.faces_uvs_list()[i], new_tex.faces_uvs_list()[i * N + n]) self.assertClose(tex_init.maps_padded()[i, ...], new_tex.maps_padded()[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.faces_uvs_padded(), new_tex.faces_uvs_padded(), tex_init.faces_uvs_packed(), new_tex.faces_uvs_packed(), tex_init.verts_uvs_padded(), new_tex.verts_uvs_padded(), tex_init.verts_uvs_packed(), new_tex.verts_uvs_packed(), tex_init.maps_padded(), new_tex.maps_padded(), ]) with self.assertRaises(ValueError): tex_mesh.extend(N=-1)
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. N = 2 faces_uvs_list = [ torch.tensor([[0, 1, 2], [3, 5, 4], [7, 6, 8]]), torch.tensor([[0, 1, 2], [3, 4, 5]]), ] # (N, 3, 3) verts_uvs_list = [torch.ones(9, 2), torch.ones(6, 2)] num_faces_per_mesh = [f.shape[0] for f in faces_uvs_list] num_verts_per_mesh = [v.shape[0] for v in verts_uvs_list] tex = TexturesUV( maps=torch.ones((N, 16, 16, 3)), faces_uvs=faces_uvs_list, verts_uvs=verts_uvs_list, ) # This is set inside Meshes when textures is passed as an input. # Here we set _num_faces_per_mesh and _num_verts_per_mesh explicity. tex1 = tex.clone() tex1._num_faces_per_mesh = num_faces_per_mesh tex1._num_verts_per_mesh = num_verts_per_mesh verts_list = tex1.verts_uvs_list() verts_padded = tex1.verts_uvs_padded() faces_list = tex1.faces_uvs_list() faces_padded = tex1.faces_uvs_padded() for f1, f2 in zip(faces_list, faces_uvs_list): self.assertTrue((f1 == f2).all().item()) for f1, f2 in zip(verts_list, verts_uvs_list): self.assertTrue((f1 == f2).all().item()) self.assertTrue(faces_padded.shape == (2, 3, 3)) self.assertTrue(verts_padded.shape == (2, 9, 2)) # Case where num_faces_per_mesh is not set and faces_verts_uvs # are initialized with a padded tensor. tex2 = TexturesUV( maps=torch.ones((N, 16, 16, 3)), verts_uvs=verts_padded, faces_uvs=faces_padded, ) faces_list = tex2.faces_uvs_list() verts_list = tex2.verts_uvs_list() for i, (f1, f2) in enumerate(zip(faces_list, faces_uvs_list)): n = num_faces_per_mesh[i] self.assertTrue((f1[:n] == f2).all().item()) for i, (f1, f2) in enumerate(zip(verts_list, verts_uvs_list)): n = num_verts_per_mesh[i] self.assertTrue((f1[:n] == f2).all().item())
def test_textures_uv_init_fail(self): # Maps has wrong shape with self.assertRaisesRegex(ValueError, "maps"): TexturesUV( maps=torch.ones((5, 16, 16, 3, 4)), faces_uvs=torch.rand(size=(5, 10, 3)), verts_uvs=torch.rand(size=(5, 15, 2)), ) # faces_uvs has wrong shape with self.assertRaisesRegex(ValueError, "faces_uvs"): TexturesUV( maps=torch.ones((5, 16, 16, 3)), faces_uvs=torch.rand(size=(5, 10, 3, 3)), verts_uvs=torch.rand(size=(5, 15, 2)), ) # verts_uvs has wrong shape with self.assertRaisesRegex(ValueError, "verts_uvs"): TexturesUV( maps=torch.ones((5, 16, 16, 3)), faces_uvs=torch.rand(size=(5, 10, 3)), verts_uvs=torch.rand(size=(5, 15, 2, 3)), ) # verts has different batch dim to faces with self.assertRaisesRegex(ValueError, "verts_uvs"): TexturesUV( maps=torch.ones((5, 16, 16, 3)), faces_uvs=torch.rand(size=(5, 10, 3)), verts_uvs=torch.rand(size=(8, 15, 2)), ) # maps has different batch dim to faces with self.assertRaisesRegex(ValueError, "maps"): TexturesUV( maps=torch.ones((8, 16, 16, 3)), faces_uvs=torch.rand(size=(5, 10, 3)), verts_uvs=torch.rand(size=(5, 15, 2)), ) # verts on different device to faces with self.assertRaisesRegex(ValueError, "verts_uvs"): TexturesUV( maps=torch.ones((5, 16, 16, 3)), faces_uvs=torch.rand(size=(5, 10, 3)), verts_uvs=torch.rand(size=(5, 15, 2, 3), device="cuda"), ) # maps on different device to faces with self.assertRaisesRegex(ValueError, "map"): TexturesUV( maps=torch.ones((5, 16, 16, 3), device="cuda"), faces_uvs=torch.rand(size=(5, 10, 3)), verts_uvs=torch.rand(size=(5, 15, 2)), )
def test_sample_textures_error(self): N = 1 V = 20 F = 10 maps = torch.rand(size=(N, 1, 1, 3)) verts_uvs = torch.randn(size=(N, V, 2)) tex = TexturesUV( maps=maps, faces_uvs=torch.randint(size=(N, 15, 3), high=V), verts_uvs=verts_uvs, ) verts = torch.rand(size=(5, V, 3)) faces = torch.randint(size=(5, 10, 3), high=V) meshes = Meshes(verts=verts, faces=faces) # Wrong number of faces with self.assertRaisesRegex(ValueError, "do not match the dimensions"): Meshes(verts=verts, faces=faces, textures=tex) # Wrong batch dim for faces tex = TexturesUV( maps=maps, faces_uvs=torch.randint(size=(1, F, 3), high=V), verts_uvs=verts_uvs, ) with self.assertRaisesRegex(ValueError, "do not match the dimensions"): Meshes(verts=verts, faces=faces, textures=tex) # Wrong batch dim for verts_uvs is not necessary to check as # there is already a check inside TexturesUV for a batch dim # mismatch with faces_uvs 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_faces_verts_textures(self): device = torch.device("cuda:0") N, V, F, H, W = 2, 5, 12, 8, 8 vert_uvs = torch.rand((N, V, 2), dtype=torch.float32, device=device) face_uvs = torch.randint(high=V, size=(N, F, 3), dtype=torch.int64, device=device) maps = torch.rand((N, H, W, 3), dtype=torch.float32, device=device) tex = TexturesUV(maps=maps, verts_uvs=vert_uvs, faces_uvs=face_uvs) # naive faces_verts_textures faces_verts_texs = [] for n in range(N): temp = torch.zeros((F, 3, 3), device=device, dtype=torch.float32) for f in range(F): uv0 = vert_uvs[n, face_uvs[n, f, 0]] uv1 = vert_uvs[n, face_uvs[n, f, 1]] uv2 = vert_uvs[n, face_uvs[n, f, 2]] idx = torch.stack((uv0, uv1, uv2), dim=0).view(1, 1, 3, 2) # 1x1x3x2 idx = idx * 2.0 - 1.0 imap = maps[n].view(1, H, W, 3).permute(0, 3, 1, 2) # 1x3xHxW imap = torch.flip(imap, [2]) texts = torch.nn.functional.grid_sample( imap, idx, align_corners=tex.align_corners, padding_mode=tex.padding_mode, ) # 1x3x1x3 temp[f] = texts[0, :, 0, :].permute(1, 0) faces_verts_texs.append(temp) faces_verts_texs = torch.cat(faces_verts_texs, 0) self.assertClose(faces_verts_texs, tex.faces_verts_textures_packed())
def test_sample_textures_uv(self): 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) dummy_verts = torch.zeros(4, 3) vert_uvs = torch.tensor([[1, 0], [0, 1], [1, 1], [0, 0]], dtype=torch.float32) face_uvs = torch.tensor([[0, 1, 2], [1, 2, 3]], dtype=torch.int64) interpolated_uvs = torch.tensor( [[0.5 + 0.2, 0.3 + 0.2], [0.6, 0.3 + 0.6]], dtype=torch.float32) # Create a dummy texture map H = 2 W = 2 x = torch.linspace(0, 1, W).view(1, W).expand(H, W) y = torch.linspace(0, 1, H).view(H, 1).expand(H, W) tex_map = torch.stack([x, y], dim=2).view(1, H, W, 2) pix_to_face = torch.tensor([0, 1], dtype=torch.int64).view(1, 1, 1, 2) fragments = Fragments( pix_to_face=pix_to_face, bary_coords=barycentric_coords, zbuf=pix_to_face, dists=pix_to_face, ) for align_corners in [True, False]: tex = TexturesUV( maps=tex_map, faces_uvs=[face_uvs], verts_uvs=[vert_uvs], align_corners=align_corners, ) meshes = Meshes(verts=[dummy_verts], faces=[face_uvs], textures=tex) mesh_textures = meshes.textures texels = mesh_textures.sample_textures(fragments) # Expected output pixel_uvs = interpolated_uvs * 2.0 - 1.0 pixel_uvs = pixel_uvs.view(2, 1, 1, 2) tex_map_ = torch.flip(tex_map, [1]).permute(0, 3, 1, 2) tex_map_ = torch.cat([tex_map_, tex_map_], dim=0) expected_out = F.grid_sample(tex_map_, pixel_uvs, align_corners=align_corners, padding_mode="border") self.assertTrue( torch.allclose(texels.squeeze(), expected_out.squeeze()))
def test_png_debug(self): maps = torch.rand(size=(1, 256, 128, 3)) * torch.tensor([0.8, 1, 0.8]) verts_uvs = torch.rand(size=(1, 20, 2)) faces_uvs = torch.zeros(size=(1, 0, 3), dtype=torch.int64) tex = TexturesUV(maps=maps, faces_uvs=faces_uvs, verts_uvs=verts_uvs) image = texturesuv_image_PIL(tex, radius=3) image_out = np.array(image) if DEBUG: image.save(DATA_DIR / "texturesuv_debug_.png") with Image.open(DATA_DIR / "texturesuv_debug.png") as image_ref_file: image_ref = np.array(image_ref_file) self.assertClose(image_out, image_ref)
def test_mesh_to(self): tex_cpu = TexturesUV( maps=torch.ones((5, 16, 16, 3)), faces_uvs=torch.randint(size=(5, 10, 3), high=15), verts_uvs=torch.rand(size=(5, 15, 2)), ) verts = torch.rand(size=(5, 15, 3)) faces = torch.randint(size=(5, 10, 3), high=15) mesh_cpu = Meshes(faces=faces, verts=verts, textures=tex_cpu) cpu = torch.device("cpu") device = torch.device("cuda:0") tex = mesh_cpu.to(device).textures self.assertEqual(tex._faces_uvs_padded.device, device) self.assertEqual(tex._verts_uvs_padded.device, device) self.assertEqual(tex._maps_padded.device, device) self.assertEqual(tex_cpu._verts_uvs_padded.device, cpu) self.assertEqual(tex_cpu.device, cpu) self.assertEqual(tex.device, device)