Skip to content

Commit 55638f3

Browse files
YangHai-1218facebook-github-bot
authored andcommitted
Support reading uv and uv map for ply format if texture_uv exists in ply file (#1100)
Summary: When the ply format looks as follows: ``` comment TextureFile ***.png element vertex 892 property double x property double y property double z property double nx property double ny property double nz property double texture_u property double texture_v ``` `MeshPlyFormat` class will read uv from the ply file and read the uv map as commented as TextureFile. Pull Request resolved: #1100 Reviewed By: MichaelRamamonjisoa Differential Revision: D50885176 Pulled By: bottler fbshipit-source-id: be75b1ec9a17a1ed87dbcf846a9072ea967aec37
1 parent f4f2209 commit 55638f3

File tree

3 files changed

+97
-6
lines changed

3 files changed

+97
-6
lines changed

pytorch3d/io/ply_io.py

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
meshes and point clouds as PLY files.
1111
"""
1212
import itertools
13+
import os
1314
import struct
1415
import sys
1516
import warnings
@@ -21,8 +22,14 @@
2122
import numpy as np
2223
import torch
2324
from iopath.common.file_io import PathManager
24-
from pytorch3d.io.utils import _check_faces_indices, _make_tensor, _open_file, PathOrStr
25-
from pytorch3d.renderer import TexturesVertex
25+
from pytorch3d.io.utils import (
26+
_check_faces_indices,
27+
_make_tensor,
28+
_open_file,
29+
_read_image,
30+
PathOrStr,
31+
)
32+
from pytorch3d.renderer import TexturesUV, TexturesVertex
2633
from pytorch3d.structures import Meshes, Pointclouds
2734

2835
from .pluggable_formats import (
@@ -804,6 +811,7 @@ class _VertsColumnIndices:
804811
color_idxs: Optional[List[int]]
805812
color_scale: float
806813
normal_idxs: Optional[List[int]]
814+
texture_uv_idxs: Optional[List[int]]
807815

808816

809817
def _get_verts_column_indices(
@@ -827,6 +835,8 @@ def _get_verts_column_indices(
827835
property uchar red
828836
property uchar green
829837
property uchar blue
838+
property double texture_u
839+
property double texture_v
830840
831841
then the return value will be ([0,1,2], [6,7,8], 1.0/255, [3,4,5])
832842
@@ -839,6 +849,7 @@ def _get_verts_column_indices(
839849
point_idxs: List[Optional[int]] = [None, None, None]
840850
color_idxs: List[Optional[int]] = [None, None, None]
841851
normal_idxs: List[Optional[int]] = [None, None, None]
852+
texture_uv_idxs: List[Optional[int]] = [None, None]
842853
for i, prop in enumerate(vertex_head.properties):
843854
if prop.list_size_type is not None:
844855
raise ValueError("Invalid vertices in file: did not expect list.")
@@ -851,6 +862,9 @@ def _get_verts_column_indices(
851862
for j, name in enumerate(["nx", "ny", "nz"]):
852863
if prop.name == name:
853864
normal_idxs[j] = i
865+
for j, name in enumerate(["texture_u", "texture_v"]):
866+
if prop.name == name:
867+
texture_uv_idxs[j] = i
854868
if None in point_idxs:
855869
raise ValueError("Invalid vertices in file.")
856870
color_scale = 1.0
@@ -864,6 +878,7 @@ def _get_verts_column_indices(
864878
color_idxs=None if None in color_idxs else color_idxs,
865879
color_scale=color_scale,
866880
normal_idxs=None if None in normal_idxs else normal_idxs,
881+
texture_uv_idxs=None if None in texture_uv_idxs else texture_uv_idxs,
867882
)
868883

869884

@@ -880,6 +895,7 @@ class _VertsData:
880895
verts: torch.Tensor
881896
verts_colors: Optional[torch.Tensor] = None
882897
verts_normals: Optional[torch.Tensor] = None
898+
verts_texture_uvs: Optional[torch.Tensor] = None
883899

884900

885901
def _get_verts(header: _PlyHeader, elements: dict) -> _VertsData:
@@ -922,6 +938,7 @@ def _get_verts(header: _PlyHeader, elements: dict) -> _VertsData:
922938

923939
vertex_colors = None
924940
vertex_normals = None
941+
vertex_texture_uvs = None
925942

926943
if len(vertex) == 1:
927944
# This is the case where the whole vertex element has one type,
@@ -935,6 +952,10 @@ def _get_verts(header: _PlyHeader, elements: dict) -> _VertsData:
935952
vertex_normals = torch.tensor(
936953
vertex[0][:, column_idxs.normal_idxs], dtype=torch.float32
937954
)
955+
if column_idxs.texture_uv_idxs is not None:
956+
vertex_texture_uvs = torch.tensor(
957+
vertex[0][:, column_idxs.texture_uv_idxs], dtype=torch.float32
958+
)
938959
else:
939960
# The vertex element is heterogeneous. It was read as several arrays,
940961
# part by part, where a part is a set of properties with the same type.
@@ -973,11 +994,19 @@ def _get_verts(header: _PlyHeader, elements: dict) -> _VertsData:
973994
for axis in range(3):
974995
partnum, col = prop_to_partnum_col[column_idxs.normal_idxs[axis]]
975996
vertex_normals.numpy()[:, axis] = vertex[partnum][:, col]
976-
997+
if column_idxs.texture_uv_idxs is not None:
998+
vertex_texture_uvs = torch.empty(
999+
size=(vertex_head.count, 2),
1000+
dtype=torch.float32,
1001+
)
1002+
for axis in range(2):
1003+
partnum, col = prop_to_partnum_col[column_idxs.texture_uv_idxs[axis]]
1004+
vertex_texture_uvs.numpy()[:, axis] = vertex[partnum][:, col]
9771005
return _VertsData(
9781006
verts=verts,
9791007
verts_colors=vertex_colors,
9801008
verts_normals=vertex_normals,
1009+
verts_texture_uvs=vertex_texture_uvs,
9811010
)
9821011

9831012

@@ -998,6 +1027,7 @@ class _PlyData:
9981027
faces: Optional[torch.Tensor]
9991028
verts_colors: Optional[torch.Tensor]
10001029
verts_normals: Optional[torch.Tensor]
1030+
verts_texture_uvs: Optional[torch.Tensor]
10011031

10021032

10031033
def _load_ply(f, *, path_manager: PathManager) -> _PlyData:
@@ -1358,8 +1388,27 @@ def read(
13581388
faces = torch.zeros(0, 3, dtype=torch.int64)
13591389

13601390
texture = None
1361-
if include_textures and data.verts_colors is not None:
1362-
texture = TexturesVertex([data.verts_colors.to(device)])
1391+
if include_textures:
1392+
if data.verts_colors is not None:
1393+
texture = TexturesVertex([data.verts_colors.to(device)])
1394+
elif data.verts_texture_uvs is not None:
1395+
texture_file_path = None
1396+
for comment in data.header.comments:
1397+
if "TextureFile" in comment:
1398+
given_texture_file = comment.split(" ")[-1]
1399+
texture_file_path = os.path.join(
1400+
os.path.dirname(str(path)), given_texture_file
1401+
)
1402+
if texture_file_path is not None:
1403+
texture_map = _read_image(
1404+
texture_file_path, path_manager, format="RGB"
1405+
)
1406+
texture_map = torch.tensor(texture_map, dtype=torch.float32) / 255.0
1407+
texture = TexturesUV(
1408+
[texture_map.to(device)],
1409+
[faces.to(device)],
1410+
[data.verts_texture_uvs.to(device)],
1411+
)
13631412

13641413
verts_normals = None
13651414
if data.verts_normals is not None:

tests/data/uvs.ply

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
ply
2+
format ascii 1.0
3+
comment made by Greg Turk
4+
comment this file is a cube
5+
comment TextureFile test_nd_sphere.png
6+
element vertex 8
7+
property float x
8+
property float y
9+
property float z
10+
property float texture_u
11+
property float texture_v
12+
element face 6
13+
property list uchar int vertex_index
14+
end_header
15+
0 0 0 0 0
16+
0 0 1 0.2 0.3
17+
0 1 1 0.2 0.3
18+
0 1 0 0.2 0.3
19+
1 0 0 0.2 0.3
20+
1 0 1 0.2 0.3
21+
1 1 1 0.2 0.3
22+
1 1 0 0.4 0.5
23+
4 0 1 2 3
24+
4 7 6 5 4
25+
4 0 4 5 1
26+
4 1 5 6 2
27+
4 2 6 7 3
28+
4 3 7 4 0

tests/test_io_ply.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020
from pytorch3d.structures import Meshes, Pointclouds
2121
from pytorch3d.utils import torus
2222

23-
from .common_testing import TestCaseMixin
23+
from .common_testing import get_tests_dir, TestCaseMixin
2424

2525

2626
global_path_manager = PathManager()
27+
DATA_DIR = get_tests_dir() / "data"
2728

2829

2930
def _load_ply_raw(stream):
@@ -778,6 +779,19 @@ def test_load_simple_binary(self):
778779
data["minus_ones"], [-1, 255, -1, 65535, -1, 4294967295]
779780
)
780781

782+
def test_load_uvs(self):
783+
io = IO()
784+
mesh = io.load_mesh(DATA_DIR / "uvs.ply")
785+
self.assertEqual(mesh.textures.verts_uvs_padded().shape, (1, 8, 2))
786+
self.assertClose(
787+
mesh.textures.verts_uvs_padded()[0],
788+
torch.tensor([[0, 0]] + [[0.2, 0.3]] * 6 + [[0.4, 0.5]]),
789+
)
790+
self.assertEqual(
791+
mesh.textures.faces_uvs_padded().shape, mesh.faces_padded().shape
792+
)
793+
self.assertEqual(mesh.textures.maps_padded().shape, (1, 512, 512, 3))
794+
781795
def test_bad_ply_syntax(self):
782796
"""Some syntactically bad ply files."""
783797
lines = [

0 commit comments

Comments
 (0)