Skip to content

Commit 25c065e

Browse files
bottlerfacebook-github-bot
authored andcommitted
PathManager passing
Summary: Make no internal functions inside pytorch3d/io interpret str paths except using a PathManager from iopath which they have been given. This means we no longer use any global PathManager object and we no longer use fvcore's deprecated file_io. To preserve the APIs, various top level functions create their own default-initialized PathManager object if they are not provided one. Reviewed By: theschnitz Differential Revision: D25372969 fbshipit-source-id: c176ee31439645fa54a157d6f1aef18b09501569
1 parent b956215 commit 25c065e

File tree

6 files changed

+108
-27
lines changed

6 files changed

+108
-27
lines changed

pytorch3d/io/mtl_io.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import numpy as np
99
import torch
1010
import torch.nn.functional as F
11+
from iopath.common.file_io import PathManager
1112
from pytorch3d.io.utils import _open_file, _read_image
1213

1314

@@ -391,12 +392,14 @@ def _bilinear_interpolation_grid_sample(
391392
TextureImages = Dict[str, torch.Tensor]
392393

393394

394-
def _parse_mtl(f, device="cpu") -> Tuple[MaterialProperties, TextureFiles]:
395+
def _parse_mtl(
396+
f, path_manager: PathManager, device="cpu"
397+
) -> Tuple[MaterialProperties, TextureFiles]:
395398
material_properties = {}
396399
texture_files = {}
397400
material_name = ""
398401

399-
with _open_file(f, "r") as f:
402+
with _open_file(f, path_manager, "r") as f:
400403
for line in f:
401404
tokens = line.strip().split()
402405
if not tokens:
@@ -438,6 +441,7 @@ def _load_texture_images(
438441
data_dir: str,
439442
material_properties: MaterialProperties,
440443
texture_files: TextureFiles,
444+
path_manager: PathManager,
441445
) -> Tuple[MaterialProperties, TextureImages]:
442446
final_material_properties = {}
443447
texture_images = {}
@@ -448,7 +452,9 @@ def _load_texture_images(
448452
# Load the texture image.
449453
path = os.path.join(data_dir, texture_files[material_name])
450454
if os.path.isfile(path):
451-
image = _read_image(path, format="RGB") / 255.0
455+
image = (
456+
_read_image(path, path_manager=path_manager, format="RGB") / 255.0
457+
)
452458
image = torch.from_numpy(image)
453459
texture_images[material_name] = image
454460
else:
@@ -464,7 +470,12 @@ def _load_texture_images(
464470

465471

466472
def load_mtl(
467-
f, material_names: List[str], data_dir: str, device="cpu"
473+
f,
474+
*,
475+
material_names: List[str],
476+
data_dir: str,
477+
device="cpu",
478+
path_manager: PathManager,
468479
) -> Tuple[MaterialProperties, TextureImages]:
469480
"""
470481
Load texture images and material reflectivity values for ambient, diffuse
@@ -474,6 +485,7 @@ def load_mtl(
474485
f: a file-like object of the material information.
475486
material_names: a list of the material names found in the .obj file.
476487
data_dir: the directory where the material texture files are located.
488+
path_manager: PathManager for interpreting both f and material_names.
477489
478490
Returns:
479491
material_properties: dict of properties for each material. If a material
@@ -494,7 +506,11 @@ def load_mtl(
494506
...
495507
}
496508
"""
497-
material_properties, texture_files = _parse_mtl(f, device)
509+
material_properties, texture_files = _parse_mtl(f, path_manager, device)
498510
return _load_texture_images(
499-
material_names, data_dir, material_properties, texture_files
511+
material_names,
512+
data_dir,
513+
material_properties,
514+
texture_files,
515+
path_manager=path_manager,
500516
)

pytorch3d/io/obj_io.py

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import numpy as np
1111
import torch
12+
from iopath.common.file_io import PathManager
1213
from pytorch3d.io.mtl_io import load_mtl, make_mesh_texture_atlas
1314
from pytorch3d.io.utils import _check_faces_indices, _make_tensor, _open_file
1415
from pytorch3d.renderer import TexturesAtlas, TexturesUV
@@ -68,6 +69,7 @@ def load_obj(
6869
texture_atlas_size: int = 4,
6970
texture_wrap: Optional[str] = "repeat",
7071
device="cpu",
72+
path_manager: Optional[PathManager] = None,
7173
):
7274
"""
7375
Load a mesh from a .obj file and optionally textures from a .mtl file.
@@ -139,6 +141,7 @@ def load_obj(
139141
If `texture_mode="clamp"` the values are clamped to the range [0, 1].
140142
If None, then there is no transformation of the texture values.
141143
device: string or torch.device on which to return the new tensors.
144+
path_manager: optionally a PathManager object to interpret paths.
142145
143146
Returns:
144147
6-element tuple containing
@@ -207,14 +210,17 @@ def load_obj(
207210
# pyre-fixme[6]: Expected `_PathLike[Variable[typing.AnyStr <: [str,
208211
# bytes]]]` for 1st param but got `Union[_PathLike[typing.Any], bytes, str]`.
209212
data_dir = os.path.dirname(f)
210-
with _open_file(f, "r") as f:
213+
if path_manager is None:
214+
path_manager = PathManager()
215+
with _open_file(f, path_manager, "r") as f:
211216
return _load_obj(
212217
f,
213-
data_dir,
218+
data_dir=data_dir,
214219
load_textures=load_textures,
215220
create_texture_atlas=create_texture_atlas,
216221
texture_atlas_size=texture_atlas_size,
217222
texture_wrap=texture_wrap,
223+
path_manager=path_manager,
218224
device=device,
219225
)
220226

@@ -226,6 +232,7 @@ def load_objs_as_meshes(
226232
create_texture_atlas: bool = False,
227233
texture_atlas_size: int = 4,
228234
texture_wrap: Optional[str] = "repeat",
235+
path_manager: Optional[PathManager] = None,
229236
):
230237
"""
231238
Load meshes from a list of .obj files using the load_obj function, and
@@ -234,11 +241,13 @@ def load_objs_as_meshes(
234241
details. material_colors and normals are not stored.
235242
236243
Args:
237-
f: A list of file-like objects (with methods read, readline, tell,
238-
and seek), pathlib paths or strings containing file names.
244+
files: A list of file-like objects (with methods read, readline, tell,
245+
and seek), pathlib paths or strings containing file names.
239246
device: Desired device of returned Meshes. Default:
240247
uses the current device for the default tensor type.
241248
load_textures: Boolean indicating whether material files are loaded
249+
create_texture_atlas, texture_atlas_size, texture_wrap: as for load_obj.
250+
path_manager: optionally a PathManager object to interpret paths.
242251
243252
Returns:
244253
New Meshes object.
@@ -251,6 +260,7 @@ def load_objs_as_meshes(
251260
create_texture_atlas=create_texture_atlas,
252261
texture_atlas_size=texture_atlas_size,
253262
texture_wrap=texture_wrap,
263+
path_manager=path_manager,
254264
)
255265
tex = None
256266
if create_texture_atlas:
@@ -431,7 +441,13 @@ def _parse_obj(f, data_dir: str):
431441

432442

433443
def _load_materials(
434-
material_names: List[str], f, data_dir: str, *, load_textures: bool, device
444+
material_names: List[str],
445+
f,
446+
*,
447+
data_dir: str,
448+
load_textures: bool,
449+
device,
450+
path_manager: PathManager,
435451
):
436452
"""
437453
Load materials and optionally textures from the specified path.
@@ -442,6 +458,7 @@ def _load_materials(
442458
data_dir: the directory where the material texture files are located.
443459
load_textures: whether textures should be loaded.
444460
device: string or torch.device on which to return the new tensors.
461+
path_manager: PathManager object to interpret paths.
445462
446463
Returns:
447464
material_colors: dict of properties for each material.
@@ -460,16 +477,24 @@ def _load_materials(
460477
return None, None
461478

462479
# Texture mode uv wrap
463-
return load_mtl(f, material_names, data_dir, device=device)
480+
return load_mtl(
481+
f,
482+
material_names=material_names,
483+
data_dir=data_dir,
484+
path_manager=path_manager,
485+
device=device,
486+
)
464487

465488

466489
def _load_obj(
467490
f_obj,
491+
*,
468492
data_dir,
469493
load_textures: bool = True,
470494
create_texture_atlas: bool = False,
471495
texture_atlas_size: int = 4,
472496
texture_wrap: Optional[str] = "repeat",
497+
path_manager: PathManager,
473498
device="cpu",
474499
):
475500
"""
@@ -522,7 +547,12 @@ def _load_obj(
522547

523548
texture_atlas = None
524549
material_colors, texture_images = _load_materials(
525-
material_names, mtl_path, data_dir, load_textures=load_textures, device=device
550+
material_names,
551+
mtl_path,
552+
data_dir=data_dir,
553+
load_textures=load_textures,
554+
path_manager=path_manager,
555+
device=device,
526556
)
527557

528558
if create_texture_atlas:
@@ -562,7 +592,13 @@ def _load_obj(
562592
return verts, faces, aux
563593

564594

565-
def save_obj(f, verts, faces, decimal_places: Optional[int] = None):
595+
def save_obj(
596+
f,
597+
verts,
598+
faces,
599+
decimal_places: Optional[int] = None,
600+
path_manager: Optional[PathManager] = None,
601+
):
566602
"""
567603
Save a mesh to an .obj file.
568604
@@ -571,6 +607,8 @@ def save_obj(f, verts, faces, decimal_places: Optional[int] = None):
571607
verts: FloatTensor of shape (V, 3) giving vertex coordinates.
572608
faces: LongTensor of shape (F, 3) giving faces.
573609
decimal_places: Number of decimal places for saving.
610+
path_manager: Optional PathManager for interpreting f if
611+
it is a str.
574612
"""
575613
if len(verts) and not (verts.dim() == 2 and verts.size(1) == 3):
576614
message = "Argument 'verts' should either be empty or of shape (num_verts, 3)."
@@ -580,7 +618,10 @@ def save_obj(f, verts, faces, decimal_places: Optional[int] = None):
580618
message = "Argument 'faces' should either be empty or of shape (num_faces, 3)."
581619
raise ValueError(message)
582620

583-
with _open_file(f, "w") as f:
621+
if path_manager is None:
622+
path_manager = PathManager()
623+
624+
with _open_file(f, path_manager, "w") as f:
584625
return _save(f, verts, faces, decimal_places)
585626

586627

pytorch3d/io/ply_io.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import numpy as np
1515
import torch
16+
from iopath.common.file_io import PathManager
1617
from pytorch3d.io.utils import _check_faces_indices, _make_tensor, _open_file
1718

1819

@@ -585,7 +586,7 @@ def _load_ply_raw_stream(f) -> Tuple[_PlyHeader, dict]:
585586
return header, elements
586587

587588

588-
def _load_ply_raw(f) -> Tuple[_PlyHeader, dict]:
589+
def _load_ply_raw(f, path_manager: PathManager) -> Tuple[_PlyHeader, dict]:
589590
"""
590591
Load the data from a .ply file.
591592
@@ -594,6 +595,7 @@ def _load_ply_raw(f) -> Tuple[_PlyHeader, dict]:
594595
tell and seek), a pathlib path or a string containing a file name.
595596
If the ply file is binary, a text stream is not supported.
596597
It is recommended to use a binary stream.
598+
path_manager: PathManager for loading if f is a str.
597599
598600
Returns:
599601
header: A _PlyHeader object describing the metadata in the ply file.
@@ -602,12 +604,12 @@ def _load_ply_raw(f) -> Tuple[_PlyHeader, dict]:
602604
uniformly-sized list, then the value will be a 2D numpy array.
603605
If not, it is a list of the relevant property values.
604606
"""
605-
with _open_file(f, "rb") as f:
607+
with _open_file(f, path_manager, "rb") as f:
606608
header, elements = _load_ply_raw_stream(f)
607609
return header, elements
608610

609611

610-
def load_ply(f):
612+
def load_ply(f, path_manager: Optional[PathManager] = None):
611613
"""
612614
Load the data from a .ply file.
613615
@@ -645,12 +647,16 @@ def load_ply(f):
645647
If the ply file is in the binary ply format rather than the text
646648
ply format, then a text stream is not supported.
647649
It is easiest to use a binary stream in all cases.
650+
path_manager: PathManager for loading if f is a str.
651+
648652
649653
Returns:
650654
verts: FloatTensor of shape (V, 3).
651655
faces: LongTensor of vertex indices, shape (F, 3).
652656
"""
653-
header, elements = _load_ply_raw(f)
657+
if path_manager is None:
658+
path_manager = PathManager()
659+
header, elements = _load_ply_raw(f, path_manager=path_manager)
654660

655661
vertex = elements.get("vertex", None)
656662
if vertex is None:
@@ -780,6 +786,7 @@ def save_ply(
780786
verts_normals: Optional[torch.Tensor] = None,
781787
ascii: bool = False,
782788
decimal_places: Optional[int] = None,
789+
path_manager: Optional[PathManager] = None,
783790
) -> None:
784791
"""
785792
Save a mesh to a .ply file.
@@ -791,6 +798,8 @@ def save_ply(
791798
verts_normals: FloatTensor of shape (V, 3) giving vertex normals.
792799
ascii: (bool) whether to use the ascii ply format.
793800
decimal_places: Number of decimal places for saving if ascii=True.
801+
path_manager: PathManager for interpreting f if it is a str.
802+
794803
"""
795804

796805
verts_normals = (
@@ -816,5 +825,7 @@ def save_ply(
816825
message = "Argument 'verts_normals' should either be empty or of shape (num_verts, 3)."
817826
raise ValueError(message)
818827

819-
with _open_file(f, "wb") as f:
828+
if path_manager is None:
829+
path_manager = PathManager()
830+
with _open_file(f, path_manager, "wb") as f:
820831
_save_ply(f, verts, faces, verts_normals, ascii, decimal_places)

pytorch3d/io/utils.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import numpy as np
99
import torch
10-
from fvcore.common.file_io import PathManager
10+
from iopath.common.file_io import PathManager
1111
from PIL import Image
1212

1313

@@ -19,9 +19,9 @@ def nullcontext(x):
1919
yield x
2020

2121

22-
def _open_file(f, mode="r") -> ContextManager[IO]:
22+
def _open_file(f, path_manager: PathManager, mode="r") -> ContextManager[IO]:
2323
if isinstance(f, str):
24-
f = open(f, mode)
24+
f = path_manager.open(f, mode)
2525
return contextlib.closing(f)
2626
elif isinstance(f, pathlib.Path):
2727
f = f.open(mode)
@@ -58,18 +58,19 @@ def _check_faces_indices(
5858
return faces_indices
5959

6060

61-
def _read_image(file_name: str, format=None):
61+
def _read_image(file_name: str, path_manager: PathManager, format=None):
6262
"""
6363
Read an image from a file using Pillow.
6464
Args:
6565
file_name: image file path.
66+
path_manager: PathManager for interpreting file_name.
6667
format: one of ["RGB", "BGR"]
6768
Returns:
6869
image: an image of shape (H, W, C).
6970
"""
7071
if format not in ["RGB", "BGR"]:
7172
raise ValueError("format can only be one of [RGB, BGR]; got %s", format)
72-
with PathManager.open(file_name, "rb") as f:
73+
with path_manager.open(file_name, "rb") as f:
7374
# pyre-fixme[6]: Expected `Union[str, typing.BinaryIO]` for 1st param but
7475
# got `Union[typing.IO[bytes], typing.IO[str]]`.
7576
image = Image.open(f)

0 commit comments

Comments
 (0)