mirror of
https://github.com/facebookresearch/pytorch3d.git
synced 2025-08-02 03:42:50 +08:00
Address black + isort fbsource linter warnings
Summary: Address black + isort fbsource linter warnings from D20558374 (previous diff) Reviewed By: nikhilaravi Differential Revision: D20558373 fbshipit-source-id: d3607de4a01fb24c0d5269634563a7914bddf1c8
This commit is contained in:
parent
eb512ffde3
commit
d57daa6f85
@ -8,6 +8,7 @@ TODO: python 3.8 when pytorch 1.4.
|
||||
"""
|
||||
|
||||
import os.path
|
||||
|
||||
import jinja2
|
||||
import yaml
|
||||
|
||||
@ -45,9 +46,7 @@ def workflow_pair(
|
||||
):
|
||||
|
||||
w = []
|
||||
base_workflow_name = (
|
||||
f"{prefix}binary_linux_{btype}_py{python_version}_{cu_version}"
|
||||
)
|
||||
base_workflow_name = f"{prefix}binary_linux_{btype}_py{python_version}_{cu_version}"
|
||||
|
||||
w.append(
|
||||
generate_base_workflow(
|
||||
@ -94,9 +93,7 @@ def generate_base_workflow(
|
||||
return {f"binary_linux_{btype}": d}
|
||||
|
||||
|
||||
def generate_upload_workflow(
|
||||
*, base_workflow_name, btype, cu_version, filter_branch
|
||||
):
|
||||
def generate_upload_workflow(*, base_workflow_name, btype, cu_version, filter_branch):
|
||||
d = {
|
||||
"name": f"{base_workflow_name}_upload",
|
||||
"context": "org-member",
|
||||
|
@ -22,6 +22,7 @@ from recommonmark.states import DummyStateMachine
|
||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||
from sphinx.ext.autodoc import between
|
||||
|
||||
|
||||
# Monkey patch to fix recommonmark 0.4 doc reference issues.
|
||||
orig_run_role = DummyStateMachine.run_role
|
||||
|
||||
@ -154,9 +155,7 @@ html_theme_options = {"collapse_navigation": True}
|
||||
def url_resolver(url):
|
||||
if ".html" not in url:
|
||||
url = url.replace("../", "")
|
||||
return (
|
||||
"https://github.com/facebookresearch/pytorch3d/blob/master/" + url
|
||||
)
|
||||
return "https://github.com/facebookresearch/pytorch3d/blob/master/" + url
|
||||
else:
|
||||
if DEPLOY:
|
||||
return "http://pytorch3d.readthedocs.io/" + url
|
||||
@ -188,9 +187,7 @@ def setup(app):
|
||||
|
||||
# Register a sphinx.ext.autodoc.between listener to ignore everything
|
||||
# between lines that contain the word IGNORE
|
||||
app.connect(
|
||||
"autodoc-process-docstring", between("^.*IGNORE.*$", exclude=True)
|
||||
)
|
||||
app.connect("autodoc-process-docstring", between("^.*IGNORE.*$", exclude=True))
|
||||
app.add_transform(AutoStructify)
|
||||
|
||||
return app
|
||||
|
@ -15,7 +15,7 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Render a coloured point cloud\n",
|
||||
"# Render a colored point cloud\n",
|
||||
"\n",
|
||||
"This tutorial shows how to:\n",
|
||||
"- set up a renderer \n",
|
||||
@ -84,7 +84,7 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Load a point cloud and corresponding colours\n",
|
||||
"### Load a point cloud and corresponding colors\n",
|
||||
"\n",
|
||||
"Load a `.ply` file and create a **Point Cloud** object. \n",
|
||||
"\n",
|
@ -1,8 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from .camera_visualization import (
|
||||
get_camera_wireframe,
|
||||
plot_camera_scene,
|
||||
plot_cameras,
|
||||
)
|
||||
from .camera_visualization import get_camera_wireframe, plot_camera_scene, plot_cameras
|
||||
from .plot_image_grid import image_grid
|
||||
|
@ -34,13 +34,9 @@ def image_grid(
|
||||
cols = 1
|
||||
|
||||
gridspec_kw = {"wspace": 0.0, "hspace": 0.0} if fill else {}
|
||||
fig, axarr = plt.subplots(
|
||||
rows, cols, gridspec_kw=gridspec_kw, figsize=(15, 9)
|
||||
)
|
||||
fig, axarr = plt.subplots(rows, cols, gridspec_kw=gridspec_kw, figsize=(15, 9))
|
||||
bleed = 0
|
||||
fig.subplots_adjust(
|
||||
left=bleed, bottom=bleed, right=(1 - bleed), top=(1 - bleed)
|
||||
)
|
||||
fig.subplots_adjust(left=bleed, bottom=bleed, right=(1 - bleed), top=(1 - bleed))
|
||||
|
||||
for ax, im in zip(axarr.ravel(), images):
|
||||
if rgb:
|
||||
|
@ -4,4 +4,5 @@
|
||||
from .obj_io import load_obj, load_objs_as_meshes, save_obj
|
||||
from .ply_io import load_ply, save_ply
|
||||
|
||||
|
||||
__all__ = [k for k in globals().keys() if not k.startswith("_")]
|
||||
|
@ -2,16 +2,16 @@
|
||||
|
||||
|
||||
"""This module implements utility functions for loading and saving meshes."""
|
||||
import numpy as np
|
||||
import os
|
||||
import pathlib
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from fvcore.common.file_io import PathManager
|
||||
from PIL import Image
|
||||
|
||||
from pytorch3d.structures import Meshes, Textures, join_meshes
|
||||
|
||||
|
||||
@ -51,9 +51,7 @@ def _read_image(file_name: str, format=None):
|
||||
|
||||
# Faces & Aux type returned from load_obj function.
|
||||
_Faces = namedtuple("Faces", "verts_idx normals_idx textures_idx materials_idx")
|
||||
_Aux = namedtuple(
|
||||
"Properties", "normals verts_uvs material_colors texture_images"
|
||||
)
|
||||
_Aux = namedtuple("Properties", "normals verts_uvs material_colors texture_images")
|
||||
|
||||
|
||||
def _format_faces_indices(faces_indices, max_index):
|
||||
@ -247,9 +245,7 @@ def load_objs_as_meshes(files: list, device=None, load_textures: bool = True):
|
||||
image = list(tex_maps.values())[0].to(device)[None]
|
||||
tex = Textures(verts_uvs=verts_uvs, faces_uvs=faces_uvs, maps=image)
|
||||
|
||||
mesh = Meshes(
|
||||
verts=[verts], faces=[faces.verts_idx.to(device)], textures=tex
|
||||
)
|
||||
mesh = Meshes(verts=[verts], faces=[faces.verts_idx.to(device)], textures=tex)
|
||||
mesh_list.append(mesh)
|
||||
if len(mesh_list) == 1:
|
||||
return mesh_list[0]
|
||||
@ -308,9 +304,7 @@ def _parse_face(
|
||||
# Subdivide faces with more than 3 vertices. See comments of the
|
||||
# load_obj function for more details.
|
||||
for i in range(len(face_verts) - 2):
|
||||
faces_verts_idx.append(
|
||||
(face_verts[0], face_verts[i + 1], face_verts[i + 2])
|
||||
)
|
||||
faces_verts_idx.append((face_verts[0], face_verts[i + 1], face_verts[i + 2]))
|
||||
if len(face_normals) > 0:
|
||||
faces_normals_idx.append(
|
||||
(face_normals[0], face_normals[i + 1], face_normals[i + 2])
|
||||
@ -367,8 +361,7 @@ def _load(f_obj, data_dir, load_textures=True):
|
||||
tx = [float(x) for x in line.split()[1:3]]
|
||||
if len(tx) != 2:
|
||||
raise ValueError(
|
||||
"Texture %s does not have 2 values. Line: %s"
|
||||
% (str(tx), str(line))
|
||||
"Texture %s does not have 2 values. Line: %s" % (str(tx), str(line))
|
||||
)
|
||||
verts_uvs.append(tx)
|
||||
elif line.startswith("vn "):
|
||||
@ -397,17 +390,13 @@ def _load(f_obj, data_dir, load_textures=True):
|
||||
|
||||
# Repeat for normals and textures if present.
|
||||
if len(faces_normals_idx) > 0:
|
||||
faces_normals_idx = _format_faces_indices(
|
||||
faces_normals_idx, normals.shape[0]
|
||||
)
|
||||
faces_normals_idx = _format_faces_indices(faces_normals_idx, normals.shape[0])
|
||||
if len(faces_textures_idx) > 0:
|
||||
faces_textures_idx = _format_faces_indices(
|
||||
faces_textures_idx, verts_uvs.shape[0]
|
||||
)
|
||||
if len(faces_materials_idx) > 0:
|
||||
faces_materials_idx = torch.tensor(
|
||||
faces_materials_idx, dtype=torch.int64
|
||||
)
|
||||
faces_materials_idx = torch.tensor(faces_materials_idx, dtype=torch.int64)
|
||||
|
||||
# Load materials
|
||||
material_colors, texture_images = None, None
|
||||
|
@ -4,15 +4,17 @@
|
||||
|
||||
|
||||
"""This module implements utility functions for loading and saving meshes."""
|
||||
import numpy as np
|
||||
import pathlib
|
||||
import struct
|
||||
import sys
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
_PlyTypeData = namedtuple("_PlyTypeData", "size struct_char np_type")
|
||||
|
||||
_PLY_TYPES = {
|
||||
@ -257,11 +259,7 @@ def _try_read_ply_constant_list_ascii(f, definition: _PlyElementType):
|
||||
"ignore", message=".* Empty input file.*", category=UserWarning
|
||||
)
|
||||
data = np.loadtxt(
|
||||
f,
|
||||
dtype=np_type,
|
||||
comments=None,
|
||||
ndmin=2,
|
||||
max_rows=definition.count,
|
||||
f, dtype=np_type, comments=None, ndmin=2, max_rows=definition.count
|
||||
)
|
||||
except ValueError:
|
||||
f.seek(start_point)
|
||||
@ -301,9 +299,7 @@ def _parse_heterogenous_property_ascii(datum, line_iter, property: _Property):
|
||||
length = int(value)
|
||||
except ValueError:
|
||||
raise ValueError("A list length was not a number.")
|
||||
list_value = np.zeros(
|
||||
length, dtype=_PLY_TYPES[property.data_type].np_type
|
||||
)
|
||||
list_value = np.zeros(length, dtype=_PLY_TYPES[property.data_type].np_type)
|
||||
for i in range(length):
|
||||
inner_value = next(line_iter, None)
|
||||
if inner_value is None:
|
||||
@ -404,8 +400,7 @@ def _read_ply_element_struct(f, definition: _PlyElementType, endian_str: str):
|
||||
values. There is one column for each property.
|
||||
"""
|
||||
format = "".join(
|
||||
_PLY_TYPES[property.data_type].struct_char
|
||||
for property in definition.properties
|
||||
_PLY_TYPES[property.data_type].struct_char for property in definition.properties
|
||||
)
|
||||
format = endian_str + format
|
||||
pattern = struct.Struct(format)
|
||||
@ -414,10 +409,7 @@ def _read_ply_element_struct(f, definition: _PlyElementType, endian_str: str):
|
||||
bytes_data = f.read(needed_bytes)
|
||||
if len(bytes_data) != needed_bytes:
|
||||
raise ValueError("Not enough data for %s." % definition.name)
|
||||
data = [
|
||||
pattern.unpack_from(bytes_data, i * size)
|
||||
for i in range(definition.count)
|
||||
]
|
||||
data = [pattern.unpack_from(bytes_data, i * size) for i in range(definition.count)]
|
||||
return data
|
||||
|
||||
|
||||
@ -475,9 +467,7 @@ def _try_read_ply_constant_list_binary(
|
||||
return output
|
||||
|
||||
|
||||
def _read_ply_element_binary(
|
||||
f, definition: _PlyElementType, big_endian: bool
|
||||
) -> list:
|
||||
def _read_ply_element_binary(f, definition: _PlyElementType, big_endian: bool) -> list:
|
||||
"""
|
||||
Decode all instances of a single element from a binary .ply file.
|
||||
|
||||
@ -515,9 +505,7 @@ def _read_ply_element_binary(
|
||||
data = []
|
||||
for _i in range(definition.count):
|
||||
datum = []
|
||||
for property, property_struct in zip(
|
||||
definition.properties, property_structs
|
||||
):
|
||||
for property, property_struct in zip(definition.properties, property_structs):
|
||||
size = property_struct.size
|
||||
initial_data = f.read(size)
|
||||
if len(initial_data) != size:
|
||||
@ -656,28 +644,19 @@ def load_ply(f):
|
||||
if face is None:
|
||||
raise ValueError("The ply file has no face element.")
|
||||
|
||||
if (
|
||||
not isinstance(vertex, np.ndarray)
|
||||
or vertex.ndim != 2
|
||||
or vertex.shape[1] != 3
|
||||
):
|
||||
if not isinstance(vertex, np.ndarray) or vertex.ndim != 2 or vertex.shape[1] != 3:
|
||||
raise ValueError("Invalid vertices in file.")
|
||||
verts = torch.tensor(vertex, dtype=torch.float32)
|
||||
|
||||
face_head = next(head for head in header.elements if head.name == "face")
|
||||
if (
|
||||
len(face_head.properties) != 1
|
||||
or face_head.properties[0].list_size_type is None
|
||||
):
|
||||
if len(face_head.properties) != 1 or face_head.properties[0].list_size_type is None:
|
||||
raise ValueError("Unexpected form of faces data.")
|
||||
# face_head.properties[0].name is usually "vertex_index" or "vertex_indices"
|
||||
# but we don't need to enforce this.
|
||||
if isinstance(face, np.ndarray) and face.ndim == 2:
|
||||
if face.shape[1] < 3:
|
||||
raise ValueError("Faces must have at least 3 vertices.")
|
||||
face_arrays = [
|
||||
face[:, [0, i + 1, i + 2]] for i in range(face.shape[1] - 2)
|
||||
]
|
||||
face_arrays = [face[:, [0, i + 1, i + 2]] for i in range(face.shape[1] - 2)]
|
||||
faces = torch.tensor(np.vstack(face_arrays), dtype=torch.int64)
|
||||
else:
|
||||
face_list = []
|
||||
@ -687,9 +666,7 @@ def load_ply(f):
|
||||
if face_item.shape[0] < 3:
|
||||
raise ValueError("Faces must have at least 3 vertices.")
|
||||
for i in range(face_item.shape[0] - 2):
|
||||
face_list.append(
|
||||
[face_item[0], face_item[i + 1], face_item[i + 2]]
|
||||
)
|
||||
face_list.append([face_item[0], face_item[i + 1], face_item[i + 2]])
|
||||
faces = torch.tensor(face_list, dtype=torch.int64)
|
||||
|
||||
return verts, faces
|
||||
|
@ -6,4 +6,5 @@ from .mesh_edge_loss import mesh_edge_loss
|
||||
from .mesh_laplacian_smoothing import mesh_laplacian_smoothing
|
||||
from .mesh_normal_consistency import mesh_normal_consistency
|
||||
|
||||
|
||||
__all__ = [k for k in globals().keys() if not k.startswith("_")]
|
||||
|
@ -2,13 +2,10 @@
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from pytorch3d.ops.nearest_neighbor_points import nn_points_idx
|
||||
|
||||
|
||||
def _validate_chamfer_reduction_inputs(
|
||||
batch_reduction: str, point_reduction: str
|
||||
):
|
||||
def _validate_chamfer_reduction_inputs(batch_reduction: str, point_reduction: str):
|
||||
"""Check the requested reductions are valid.
|
||||
|
||||
Args:
|
||||
@ -18,17 +15,11 @@ def _validate_chamfer_reduction_inputs(
|
||||
points, can be one of ["none", "mean", "sum"].
|
||||
"""
|
||||
if batch_reduction not in ["none", "mean", "sum"]:
|
||||
raise ValueError(
|
||||
'batch_reduction must be one of ["none", "mean", "sum"]'
|
||||
)
|
||||
raise ValueError('batch_reduction must be one of ["none", "mean", "sum"]')
|
||||
if point_reduction not in ["none", "mean", "sum"]:
|
||||
raise ValueError(
|
||||
'point_reduction must be one of ["none", "mean", "sum"]'
|
||||
)
|
||||
raise ValueError('point_reduction must be one of ["none", "mean", "sum"]')
|
||||
if batch_reduction == "none" and point_reduction == "none":
|
||||
raise ValueError(
|
||||
'batch_reduction and point_reduction cannot both be "none".'
|
||||
)
|
||||
raise ValueError('batch_reduction and point_reduction cannot both be "none".')
|
||||
|
||||
|
||||
def chamfer_distance(
|
||||
@ -87,10 +78,7 @@ def chamfer_distance(
|
||||
(x.sum((1, 2)) * weights).sum() * 0.0,
|
||||
(x.sum((1, 2)) * weights).sum() * 0.0,
|
||||
)
|
||||
return (
|
||||
(x.sum((1, 2)) * weights) * 0.0,
|
||||
(x.sum((1, 2)) * weights) * 0.0,
|
||||
)
|
||||
return ((x.sum((1, 2)) * weights) * 0.0, (x.sum((1, 2)) * weights) * 0.0)
|
||||
|
||||
return_normals = x_normals is not None and y_normals is not None
|
||||
cham_norm_x = x.new_zeros(())
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
from itertools import islice
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
@ -76,10 +77,7 @@ def mesh_normal_consistency(meshes):
|
||||
with torch.no_grad():
|
||||
edge_idx = face_to_edge.reshape(F * 3) # (3 * F,) indexes into edges
|
||||
vert_idx = (
|
||||
faces_packed.view(1, F, 3)
|
||||
.expand(3, F, 3)
|
||||
.transpose(0, 1)
|
||||
.reshape(3 * F, 3)
|
||||
faces_packed.view(1, F, 3).expand(3, F, 3).transpose(0, 1).reshape(3 * F, 3)
|
||||
)
|
||||
edge_idx, edge_sort_idx = edge_idx.sort()
|
||||
vert_idx = vert_idx[edge_sort_idx]
|
||||
@ -132,9 +130,7 @@ def mesh_normal_consistency(meshes):
|
||||
loss = 1 - torch.cosine_similarity(n0, n1, dim=1)
|
||||
|
||||
verts_packed_to_mesh_idx = verts_packed_to_mesh_idx[vert_idx[:, 0]]
|
||||
verts_packed_to_mesh_idx = verts_packed_to_mesh_idx[
|
||||
vert_edge_pair_idx[:, 0]
|
||||
]
|
||||
verts_packed_to_mesh_idx = verts_packed_to_mesh_idx[vert_edge_pair_idx[:, 0]]
|
||||
num_normals = verts_packed_to_mesh_idx.bincount(minlength=N)
|
||||
weights = 1.0 / num_normals[verts_packed_to_mesh_idx].float()
|
||||
|
||||
|
@ -10,4 +10,5 @@ from .sample_points_from_meshes import sample_points_from_meshes
|
||||
from .subdivide_meshes import SubdivideMeshes
|
||||
from .vert_align import vert_align
|
||||
|
||||
|
||||
__all__ = [k for k in globals().keys() if not k.startswith("_")]
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from pytorch3d.structures import Meshes
|
||||
|
||||
|
||||
@ -200,8 +199,6 @@ def cubify(voxels, thresh, device=None) -> Meshes:
|
||||
grid_verts.index_select(0, (idleverts[n] == 0).nonzero()[:, 0])
|
||||
for n in range(N)
|
||||
]
|
||||
faces_list = [
|
||||
nface - idlenum[n][nface] for n, nface in enumerate(faces_list)
|
||||
]
|
||||
faces_list = [nface - idlenum[n][nface] for n, nface in enumerate(faces_list)]
|
||||
|
||||
return Meshes(verts=verts_list, faces=faces_list)
|
||||
|
@ -3,11 +3,10 @@
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from pytorch3d import _C
|
||||
from torch.autograd import Function
|
||||
from torch.autograd.function import once_differentiable
|
||||
|
||||
from pytorch3d import _C
|
||||
|
||||
|
||||
class GraphConv(nn.Module):
|
||||
"""A single graph convolution layer."""
|
||||
@ -60,9 +59,7 @@ class GraphConv(nn.Module):
|
||||
number of output features per vertex.
|
||||
"""
|
||||
if verts.is_cuda != edges.is_cuda:
|
||||
raise ValueError(
|
||||
"verts and edges tensors must be on the same device."
|
||||
)
|
||||
raise ValueError("verts and edges tensors must be on the same device.")
|
||||
if verts.shape[0] == 0:
|
||||
# empty graph.
|
||||
return verts.new_zeros((0, self.output_dim)) * verts.sum()
|
||||
|
@ -1,7 +1,6 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import torch
|
||||
|
||||
from pytorch3d import _C
|
||||
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import torch
|
||||
from pytorch3d import _C
|
||||
from torch.autograd import Function
|
||||
from torch.autograd.function import once_differentiable
|
||||
|
||||
from pytorch3d import _C
|
||||
|
||||
|
||||
class _MeshFaceAreasNormals(Function):
|
||||
"""
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
import torch
|
||||
|
||||
from pytorch3d import _C
|
||||
|
||||
|
||||
@ -31,9 +30,7 @@ def nn_points_idx(p1, p2, p2_normals=None) -> torch.Tensor:
|
||||
"""
|
||||
N, P1, D = p1.shape
|
||||
with torch.no_grad():
|
||||
p1_nn_idx = _C.nn_points_idx(
|
||||
p1.contiguous(), p2.contiguous()
|
||||
) # (N, P1)
|
||||
p1_nn_idx = _C.nn_points_idx(p1.contiguous(), p2.contiguous()) # (N, P1)
|
||||
p1_nn_idx_expanded = p1_nn_idx.view(N, P1, 1).expand(N, P1, D)
|
||||
p1_nn_points = p2.gather(1, p1_nn_idx_expanded)
|
||||
if p2_normals is None:
|
||||
|
@ -1,11 +1,10 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import torch
|
||||
from pytorch3d import _C
|
||||
from torch.autograd import Function
|
||||
from torch.autograd.function import once_differentiable
|
||||
|
||||
from pytorch3d import _C
|
||||
|
||||
|
||||
class _PackedToPadded(Function):
|
||||
"""
|
||||
|
@ -7,8 +7,8 @@ batches of meshes.
|
||||
"""
|
||||
import sys
|
||||
from typing import Tuple, Union
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d.ops.mesh_face_areas_normals import mesh_face_areas_normals
|
||||
from pytorch3d.ops.packed_to_padded import packed_to_padded
|
||||
|
||||
@ -53,9 +53,7 @@ def sample_points_from_meshes(
|
||||
|
||||
# Only compute samples for non empty meshes
|
||||
with torch.no_grad():
|
||||
areas, _ = mesh_face_areas_normals(
|
||||
verts, faces
|
||||
) # Face areas can be zero.
|
||||
areas, _ = mesh_face_areas_normals(verts, faces) # Face areas can be zero.
|
||||
max_faces = meshes.num_faces_per_mesh().max().item()
|
||||
areas_padded = packed_to_padded(
|
||||
areas, mesh_to_face[meshes.valid], max_faces
|
||||
@ -80,21 +78,17 @@ def sample_points_from_meshes(
|
||||
a = v0[sample_face_idxs] # (N, num_samples, 3)
|
||||
b = v1[sample_face_idxs]
|
||||
c = v2[sample_face_idxs]
|
||||
samples[meshes.valid] = (
|
||||
w0[:, :, None] * a + w1[:, :, None] * b + w2[:, :, None] * c
|
||||
)
|
||||
samples[meshes.valid] = w0[:, :, None] * a + w1[:, :, None] * b + w2[:, :, None] * c
|
||||
|
||||
if return_normals:
|
||||
# Intialize normals tensor with fill value 0 for empty meshes.
|
||||
# Normals for the sampled points are face normals computed from
|
||||
# the vertices of the face in which the sampled point lies.
|
||||
normals = torch.zeros(
|
||||
(num_meshes, num_samples, 3), device=meshes.device
|
||||
)
|
||||
normals = torch.zeros((num_meshes, num_samples, 3), device=meshes.device)
|
||||
vert_normals = (v1 - v0).cross(v2 - v1, dim=1)
|
||||
vert_normals = vert_normals / vert_normals.norm(
|
||||
dim=1, p=2, keepdim=True
|
||||
).clamp(min=sys.float_info.epsilon)
|
||||
vert_normals = vert_normals / vert_normals.norm(dim=1, p=2, keepdim=True).clamp(
|
||||
min=sys.float_info.epsilon
|
||||
)
|
||||
vert_normals = vert_normals[sample_face_idxs]
|
||||
normals[meshes.valid] = vert_normals
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from pytorch3d.structures import Meshes
|
||||
|
||||
|
||||
@ -193,16 +192,12 @@ class SubdivideMeshes(nn.Module):
|
||||
edges = meshes[0].edges_packed()
|
||||
|
||||
# The set of faces is the same across the different meshes.
|
||||
new_faces = self._subdivided_faces.view(1, -1, 3).expand(
|
||||
self._N, -1, -1
|
||||
)
|
||||
new_faces = self._subdivided_faces.view(1, -1, 3).expand(self._N, -1, -1)
|
||||
|
||||
# Add one new vertex at the midpoint of each edge by taking the average
|
||||
# of the vertices that form each edge.
|
||||
new_verts = verts[:, edges].mean(dim=2)
|
||||
new_verts = torch.cat(
|
||||
[verts, new_verts], dim=1
|
||||
) # (sum(V_n)+sum(E_n), 3)
|
||||
new_verts = torch.cat([verts, new_verts], dim=1) # (sum(V_n)+sum(E_n), 3)
|
||||
new_feats = None
|
||||
|
||||
# Calculate features for new vertices.
|
||||
@ -212,15 +207,11 @@ class SubdivideMeshes(nn.Module):
|
||||
# padded, i.e. (N*V, D) to (N, V, D).
|
||||
feats = feats.view(verts.size(0), verts.size(1), feats.size(1))
|
||||
if feats.dim() != 3:
|
||||
raise ValueError(
|
||||
"features need to be of shape (N, V, D) or (N*V, D)"
|
||||
)
|
||||
raise ValueError("features need to be of shape (N, V, D) or (N*V, D)")
|
||||
|
||||
# Take average of the features at the vertices that form each edge.
|
||||
new_feats = feats[:, edges].mean(dim=2)
|
||||
new_feats = torch.cat(
|
||||
[feats, new_feats], dim=1
|
||||
) # (sum(V_n)+sum(E_n), 3)
|
||||
new_feats = torch.cat([feats, new_feats], dim=1) # (sum(V_n)+sum(E_n), 3)
|
||||
|
||||
new_meshes = Meshes(verts=new_verts, faces=new_faces)
|
||||
|
||||
@ -270,9 +261,7 @@ class SubdivideMeshes(nn.Module):
|
||||
) # (sum(V_n)+sum(E_n),)
|
||||
|
||||
verts_ordered_idx_init = torch.zeros(
|
||||
new_verts_per_mesh.sum(),
|
||||
dtype=torch.int64,
|
||||
device=meshes.device,
|
||||
new_verts_per_mesh.sum(), dtype=torch.int64, device=meshes.device
|
||||
) # (sum(V_n)+sum(E_n),)
|
||||
|
||||
# Reassign vertex indices so that existing and new vertices for each
|
||||
@ -288,9 +277,7 @@ class SubdivideMeshes(nn.Module):
|
||||
|
||||
# Calculate the indices needed to group the existing and new faces
|
||||
# for each mesh.
|
||||
face_sort_idx = create_faces_index(
|
||||
num_faces_per_mesh, device=meshes.device
|
||||
)
|
||||
face_sort_idx = create_faces_index(num_faces_per_mesh, device=meshes.device)
|
||||
|
||||
# Reorder the faces to sequentially group existing and new faces
|
||||
# for each mesh.
|
||||
@ -361,9 +348,7 @@ def create_verts_index(verts_per_mesh, edges_per_mesh, device=None):
|
||||
E = edges_per_mesh.sum() # e.g. 21
|
||||
|
||||
verts_per_mesh_cumsum = verts_per_mesh.cumsum(dim=0) # (N,) e.g. (4, 9, 15)
|
||||
edges_per_mesh_cumsum = edges_per_mesh.cumsum(
|
||||
dim=0
|
||||
) # (N,) e.g. (5, 12, 21)
|
||||
edges_per_mesh_cumsum = edges_per_mesh.cumsum(dim=0) # (N,) e.g. (5, 12, 21)
|
||||
|
||||
v_to_e_idx = verts_per_mesh_cumsum.clone()
|
||||
|
||||
@ -373,9 +358,7 @@ def create_verts_index(verts_per_mesh, edges_per_mesh, device=None):
|
||||
] # e.g. (4, 9, 15) + (0, 5, 12) = (4, 14, 27)
|
||||
|
||||
# vertex to edge offset.
|
||||
v_to_e_offset = (
|
||||
V - verts_per_mesh_cumsum
|
||||
) # e.g. 15 - (4, 9, 15) = (11, 6, 0)
|
||||
v_to_e_offset = V - verts_per_mesh_cumsum # e.g. 15 - (4, 9, 15) = (11, 6, 0)
|
||||
v_to_e_offset[1:] += edges_per_mesh_cumsum[
|
||||
:-1
|
||||
] # e.g. (11, 6, 0) + (0, 5, 12) = (11, 11, 12)
|
||||
|
@ -59,9 +59,7 @@ def vert_align(
|
||||
elif hasattr(verts, "verts_padded"):
|
||||
grid = verts.verts_padded()
|
||||
else:
|
||||
raise ValueError(
|
||||
"verts must be a tensor or have a `verts_padded` attribute"
|
||||
)
|
||||
raise ValueError("verts must be a tensor or have a `verts_padded` attribute")
|
||||
|
||||
grid = grid[:, None, :, :2] # (N, 1, V, 2)
|
||||
|
||||
|
@ -44,4 +44,5 @@ from .points import (
|
||||
)
|
||||
from .utils import TensorProperties, convert_to_tensors_and_broadcast
|
||||
|
||||
|
||||
__all__ = [k for k in globals().keys() if not k.startswith("_")]
|
||||
|
@ -1,10 +1,12 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
|
||||
import numpy as np
|
||||
from typing import NamedTuple, Sequence
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
# Example functions for blending the top K colors per pixel using the outputs
|
||||
# from rasterization.
|
||||
# NOTE: All blending function should return an RGBA image per batch element
|
||||
@ -63,9 +65,7 @@ def sigmoid_alpha_blend(colors, fragments, blend_params) -> torch.Tensor:
|
||||
3D Reasoning', ICCV 2019
|
||||
"""
|
||||
N, H, W, K = fragments.pix_to_face.shape
|
||||
pixel_colors = torch.ones(
|
||||
(N, H, W, 4), dtype=colors.dtype, device=colors.device
|
||||
)
|
||||
pixel_colors = torch.ones((N, H, W, 4), dtype=colors.dtype, device=colors.device)
|
||||
mask = fragments.pix_to_face >= 0
|
||||
|
||||
# The distance is negative if a pixel is inside a face and positive outside
|
||||
@ -124,14 +124,10 @@ def softmax_rgb_blend(
|
||||
|
||||
N, H, W, K = fragments.pix_to_face.shape
|
||||
device = fragments.pix_to_face.device
|
||||
pixel_colors = torch.ones(
|
||||
(N, H, W, 4), dtype=colors.dtype, device=colors.device
|
||||
)
|
||||
pixel_colors = torch.ones((N, H, W, 4), dtype=colors.dtype, device=colors.device)
|
||||
background = blend_params.background_color
|
||||
if not torch.is_tensor(background):
|
||||
background = torch.tensor(
|
||||
background, dtype=torch.float32, device=device
|
||||
)
|
||||
background = torch.tensor(background, dtype=torch.float32, device=device)
|
||||
|
||||
# Background color
|
||||
delta = np.exp(1e-10 / blend_params.gamma) * 1e-10
|
||||
|
@ -1,15 +1,16 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
from typing import Optional, Sequence, Tuple
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from pytorch3d.transforms import Rotate, Transform3d, Translate
|
||||
|
||||
from .utils import TensorProperties, convert_to_tensors_and_broadcast
|
||||
|
||||
|
||||
# Default values for rotation and translation matrices.
|
||||
r = np.expand_dims(np.eye(3), axis=0) # (1, 3, 3)
|
||||
t = np.expand_dims(np.zeros(3), axis=0) # (1, 3)
|
||||
@ -106,9 +107,7 @@ class OpenGLPerspectiveCameras(TensorProperties):
|
||||
aspect_ratio = kwargs.get("aspect_ratio", self.aspect_ratio)
|
||||
degrees = kwargs.get("degrees", self.degrees)
|
||||
|
||||
P = torch.zeros(
|
||||
(self._N, 4, 4), device=self.device, dtype=torch.float32
|
||||
)
|
||||
P = torch.zeros((self._N, 4, 4), device=self.device, dtype=torch.float32)
|
||||
ones = torch.ones((self._N), dtype=torch.float32, device=self.device)
|
||||
if degrees:
|
||||
fov = (np.pi / 180) * fov
|
||||
@ -204,9 +203,7 @@ class OpenGLPerspectiveCameras(TensorProperties):
|
||||
"""
|
||||
self.R = kwargs.get("R", self.R) # pyre-ignore[16]
|
||||
self.T = kwargs.get("T", self.T) # pyre-ignore[16]
|
||||
world_to_view_transform = get_world_to_view_transform(
|
||||
R=self.R, T=self.T
|
||||
)
|
||||
world_to_view_transform = get_world_to_view_transform(R=self.R, T=self.T)
|
||||
return world_to_view_transform
|
||||
|
||||
def get_full_projection_transform(self, **kwargs) -> Transform3d:
|
||||
@ -229,9 +226,7 @@ class OpenGLPerspectiveCameras(TensorProperties):
|
||||
"""
|
||||
self.R = kwargs.get("R", self.R) # pyre-ignore[16]
|
||||
self.T = kwargs.get("T", self.T) # pyre-ignore[16]
|
||||
world_to_view_transform = self.get_world_to_view_transform(
|
||||
R=self.R, T=self.T
|
||||
)
|
||||
world_to_view_transform = self.get_world_to_view_transform(R=self.R, T=self.T)
|
||||
view_to_screen_transform = self.get_projection_transform(**kwargs)
|
||||
return world_to_view_transform.compose(view_to_screen_transform)
|
||||
|
||||
@ -337,9 +332,7 @@ class OpenGLOrthographicCameras(TensorProperties):
|
||||
bottom = kwargs.get("bottom", self.bottom) # pyre-ignore[16]
|
||||
scale_xyz = kwargs.get("scale_xyz", self.scale_xyz) # pyre-ignore[16]
|
||||
|
||||
P = torch.zeros(
|
||||
(self._N, 4, 4), dtype=torch.float32, device=self.device
|
||||
)
|
||||
P = torch.zeros((self._N, 4, 4), dtype=torch.float32, device=self.device)
|
||||
ones = torch.ones((self._N), dtype=torch.float32, device=self.device)
|
||||
# NOTE: OpenGL flips handedness of coordinate system between camera
|
||||
# space and NDC space so z sign is -ve. In PyTorch3D we maintain a
|
||||
@ -417,9 +410,7 @@ class OpenGLOrthographicCameras(TensorProperties):
|
||||
"""
|
||||
self.R = kwargs.get("R", self.R) # pyre-ignore[16]
|
||||
self.T = kwargs.get("T", self.T) # pyre-ignore[16]
|
||||
world_to_view_transform = get_world_to_view_transform(
|
||||
R=self.R, T=self.T
|
||||
)
|
||||
world_to_view_transform = get_world_to_view_transform(R=self.R, T=self.T)
|
||||
return world_to_view_transform
|
||||
|
||||
def get_full_projection_transform(self, **kwargs) -> Transform3d:
|
||||
@ -442,9 +433,7 @@ class OpenGLOrthographicCameras(TensorProperties):
|
||||
"""
|
||||
self.R = kwargs.get("R", self.R) # pyre-ignore[16]
|
||||
self.T = kwargs.get("T", self.T) # pyre-ignore[16]
|
||||
world_to_view_transform = self.get_world_to_view_transform(
|
||||
R=self.R, T=self.T
|
||||
)
|
||||
world_to_view_transform = self.get_world_to_view_transform(R=self.R, T=self.T)
|
||||
view_to_screen_transform = self.get_projection_transform(**kwargs)
|
||||
return world_to_view_transform.compose(view_to_screen_transform)
|
||||
|
||||
@ -470,12 +459,7 @@ class SfMPerspectiveCameras(TensorProperties):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
focal_length=1.0,
|
||||
principal_point=((0.0, 0.0),),
|
||||
R=r,
|
||||
T=t,
|
||||
device="cpu",
|
||||
self, focal_length=1.0, principal_point=((0.0, 0.0),), R=r, T=t, device="cpu"
|
||||
):
|
||||
"""
|
||||
__init__(self, focal_length, principal_point, R, T, device) -> None
|
||||
@ -589,9 +573,7 @@ class SfMPerspectiveCameras(TensorProperties):
|
||||
"""
|
||||
self.R = kwargs.get("R", self.R) # pyre-ignore[16]
|
||||
self.T = kwargs.get("T", self.T) # pyre-ignore[16]
|
||||
world_to_view_transform = get_world_to_view_transform(
|
||||
R=self.R, T=self.T
|
||||
)
|
||||
world_to_view_transform = get_world_to_view_transform(R=self.R, T=self.T)
|
||||
return world_to_view_transform
|
||||
|
||||
def get_full_projection_transform(self, **kwargs) -> Transform3d:
|
||||
@ -610,9 +592,7 @@ class SfMPerspectiveCameras(TensorProperties):
|
||||
"""
|
||||
self.R = kwargs.get("R", self.R) # pyre-ignore[16]
|
||||
self.T = kwargs.get("T", self.T) # pyre-ignore[16]
|
||||
world_to_view_transform = self.get_world_to_view_transform(
|
||||
R=self.R, T=self.T
|
||||
)
|
||||
world_to_view_transform = self.get_world_to_view_transform(R=self.R, T=self.T)
|
||||
view_to_screen_transform = self.get_projection_transform(**kwargs)
|
||||
return world_to_view_transform.compose(view_to_screen_transform)
|
||||
|
||||
@ -638,12 +618,7 @@ class SfMOrthographicCameras(TensorProperties):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
focal_length=1.0,
|
||||
principal_point=((0.0, 0.0),),
|
||||
R=r,
|
||||
T=t,
|
||||
device="cpu",
|
||||
self, focal_length=1.0, principal_point=((0.0, 0.0),), R=r, T=t, device="cpu"
|
||||
):
|
||||
"""
|
||||
__init__(self, focal_length, principal_point, R, T, device) -> None
|
||||
@ -757,9 +732,7 @@ class SfMOrthographicCameras(TensorProperties):
|
||||
"""
|
||||
self.R = kwargs.get("R", self.R) # pyre-ignore[16]
|
||||
self.T = kwargs.get("T", self.T) # pyre-ignore[16]
|
||||
world_to_view_transform = get_world_to_view_transform(
|
||||
R=self.R, T=self.T
|
||||
)
|
||||
world_to_view_transform = get_world_to_view_transform(R=self.R, T=self.T)
|
||||
return world_to_view_transform
|
||||
|
||||
def get_full_projection_transform(self, **kwargs) -> Transform3d:
|
||||
@ -778,9 +751,7 @@ class SfMOrthographicCameras(TensorProperties):
|
||||
"""
|
||||
self.R = kwargs.get("R", self.R) # pyre-ignore[16]
|
||||
self.T = kwargs.get("T", self.T) # pyre-ignore[16]
|
||||
world_to_view_transform = self.get_world_to_view_transform(
|
||||
R=self.R, T=self.T
|
||||
)
|
||||
world_to_view_transform = self.get_world_to_view_transform(R=self.R, T=self.T)
|
||||
view_to_screen_transform = self.get_projection_transform(**kwargs)
|
||||
return world_to_view_transform.compose(view_to_screen_transform)
|
||||
|
||||
@ -990,9 +961,7 @@ def look_at_rotation(
|
||||
z_axis = F.normalize(at - camera_position, eps=1e-5)
|
||||
x_axis = F.normalize(torch.cross(up, z_axis), eps=1e-5)
|
||||
y_axis = F.normalize(torch.cross(z_axis, x_axis), eps=1e-5)
|
||||
R = torch.cat(
|
||||
(x_axis[:, None, :], y_axis[:, None, :], z_axis[:, None, :]), dim=1
|
||||
)
|
||||
R = torch.cat((x_axis[:, None, :], y_axis[:, None, :], z_axis[:, None, :]), dim=1)
|
||||
return R.transpose(1, 2)
|
||||
|
||||
|
||||
@ -1038,9 +1007,7 @@ def look_at_view_transform(
|
||||
"""
|
||||
|
||||
if eye is not None:
|
||||
broadcasted_args = convert_to_tensors_and_broadcast(
|
||||
eye, at, up, device=device
|
||||
)
|
||||
broadcasted_args = convert_to_tensors_and_broadcast(eye, at, up, device=device)
|
||||
eye, at, up = broadcasted_args
|
||||
C = eye
|
||||
else:
|
||||
|
@ -3,10 +3,11 @@
|
||||
|
||||
|
||||
from typing import NamedTuple
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d import _C
|
||||
|
||||
|
||||
# Example functions for blending the top K features per pixel using the outputs
|
||||
# from rasterization.
|
||||
# NOTE: All blending function should return a (N, H, W, C) tensor per batch element.
|
||||
@ -49,9 +50,7 @@ class _CompositeAlphaPoints(torch.autograd.Function):
|
||||
def forward(ctx, features, alphas, points_idx):
|
||||
pt_cld = _C.accum_alphacomposite(features, alphas, points_idx)
|
||||
|
||||
ctx.save_for_backward(
|
||||
features.clone(), alphas.clone(), points_idx.clone()
|
||||
)
|
||||
ctx.save_for_backward(features.clone(), alphas.clone(), points_idx.clone())
|
||||
return pt_cld
|
||||
|
||||
@staticmethod
|
||||
@ -68,9 +67,7 @@ class _CompositeAlphaPoints(torch.autograd.Function):
|
||||
return grad_features, grad_alphas, grad_points_idx, None
|
||||
|
||||
|
||||
def alpha_composite(
|
||||
pointsidx, alphas, pt_clds, blend_params=None
|
||||
) -> torch.Tensor:
|
||||
def alpha_composite(pointsidx, alphas, pt_clds, blend_params=None) -> torch.Tensor:
|
||||
"""
|
||||
Composite features within a z-buffer using alpha compositing. Given a zbuffer
|
||||
with corresponding features and weights, these values are accumulated according
|
||||
@ -131,9 +128,7 @@ class _CompositeNormWeightedSumPoints(torch.autograd.Function):
|
||||
def forward(ctx, features, alphas, points_idx):
|
||||
pt_cld = _C.accum_weightedsumnorm(features, alphas, points_idx)
|
||||
|
||||
ctx.save_for_backward(
|
||||
features.clone(), alphas.clone(), points_idx.clone()
|
||||
)
|
||||
ctx.save_for_backward(features.clone(), alphas.clone(), points_idx.clone())
|
||||
return pt_cld
|
||||
|
||||
@staticmethod
|
||||
@ -150,9 +145,7 @@ class _CompositeNormWeightedSumPoints(torch.autograd.Function):
|
||||
return grad_features, grad_alphas, grad_points_idx, None
|
||||
|
||||
|
||||
def norm_weighted_sum(
|
||||
pointsidx, alphas, pt_clds, blend_params=None
|
||||
) -> torch.Tensor:
|
||||
def norm_weighted_sum(pointsidx, alphas, pt_clds, blend_params=None) -> torch.Tensor:
|
||||
"""
|
||||
Composite features within a z-buffer using normalized weighted sum. Given a zbuffer
|
||||
with corresponding features and weights, these values are accumulated
|
||||
@ -213,9 +206,7 @@ class _CompositeWeightedSumPoints(torch.autograd.Function):
|
||||
def forward(ctx, features, alphas, points_idx):
|
||||
pt_cld = _C.accum_weightedsum(features, alphas, points_idx)
|
||||
|
||||
ctx.save_for_backward(
|
||||
features.clone(), alphas.clone(), points_idx.clone()
|
||||
)
|
||||
ctx.save_for_backward(features.clone(), alphas.clone(), points_idx.clone())
|
||||
return pt_cld
|
||||
|
||||
@staticmethod
|
||||
|
@ -114,12 +114,7 @@ def specular(
|
||||
|
||||
# Ensure all inputs have same batch dimension as points
|
||||
matched_tensors = convert_to_tensors_and_broadcast(
|
||||
points,
|
||||
color,
|
||||
direction,
|
||||
camera_position,
|
||||
shininess,
|
||||
device=points.device,
|
||||
points, color, direction, camera_position, shininess, device=points.device
|
||||
)
|
||||
_, color, direction, camera_position, shininess = matched_tensors
|
||||
|
||||
@ -201,9 +196,7 @@ class DirectionalLights(TensorProperties):
|
||||
normals=normals, color=self.diffuse_color, direction=self.direction
|
||||
)
|
||||
|
||||
def specular(
|
||||
self, normals, points, camera_position, shininess
|
||||
) -> torch.Tensor:
|
||||
def specular(self, normals, points, camera_position, shininess) -> torch.Tensor:
|
||||
return specular(
|
||||
points=points,
|
||||
normals=normals,
|
||||
@ -256,13 +249,9 @@ class PointLights(TensorProperties):
|
||||
|
||||
def diffuse(self, normals, points) -> torch.Tensor:
|
||||
direction = self.location - points
|
||||
return diffuse(
|
||||
normals=normals, color=self.diffuse_color, direction=direction
|
||||
)
|
||||
return diffuse(normals=normals, color=self.diffuse_color, direction=direction)
|
||||
|
||||
def specular(
|
||||
self, normals, points, camera_position, shininess
|
||||
) -> torch.Tensor:
|
||||
def specular(self, normals, points, camera_position, shininess) -> torch.Tensor:
|
||||
direction = self.location - points
|
||||
return specular(
|
||||
points=points,
|
||||
|
@ -1,10 +1,7 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
|
||||
from .texturing import ( # isort:skip
|
||||
interpolate_texture_map,
|
||||
interpolate_vertex_colors,
|
||||
)
|
||||
from .texturing import interpolate_texture_map, interpolate_vertex_colors # isort:skip
|
||||
from .rasterize_meshes import rasterize_meshes
|
||||
from .rasterizer import MeshRasterizer, RasterizationSettings
|
||||
from .renderer import MeshRenderer
|
||||
@ -20,4 +17,5 @@ from .shader import (
|
||||
from .shading import gouraud_shading, phong_shading
|
||||
from .utils import interpolate_face_attributes
|
||||
|
||||
|
||||
__all__ = [k for k in globals().keys() if not k.startswith("_")]
|
||||
|
@ -1,12 +1,13 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
|
||||
import numpy as np
|
||||
from typing import Optional
|
||||
import torch
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from pytorch3d import _C
|
||||
|
||||
|
||||
# TODO make the epsilon user configurable
|
||||
kEpsilon = 1e-30
|
||||
|
||||
@ -172,9 +173,7 @@ class _RasterizeFaceVerts(torch.autograd.Function):
|
||||
return pix_to_face, zbuf, barycentric_coords, dists
|
||||
|
||||
@staticmethod
|
||||
def backward(
|
||||
ctx, grad_pix_to_face, grad_zbuf, grad_barycentric_coords, grad_dists
|
||||
):
|
||||
def backward(ctx, grad_pix_to_face, grad_zbuf, grad_barycentric_coords, grad_dists):
|
||||
grad_face_verts = None
|
||||
grad_mesh_to_face_first_idx = None
|
||||
grad_num_faces_per_mesh = None
|
||||
@ -243,9 +242,7 @@ def rasterize_meshes_python(
|
||||
face_idxs = torch.full(
|
||||
(N, H, W, K), fill_value=-1, dtype=torch.int64, device=device
|
||||
)
|
||||
zbuf = torch.full(
|
||||
(N, H, W, K), fill_value=-1, dtype=torch.float32, device=device
|
||||
)
|
||||
zbuf = torch.full((N, H, W, K), fill_value=-1, dtype=torch.float32, device=device)
|
||||
bary_coords = torch.full(
|
||||
(N, H, W, K, 3), fill_value=-1, dtype=torch.float32, device=device
|
||||
)
|
||||
@ -308,9 +305,7 @@ def rasterize_meshes_python(
|
||||
continue
|
||||
|
||||
# Compute barycentric coordinates and pixel z distance.
|
||||
pxy = torch.tensor(
|
||||
[xf, yf], dtype=torch.float32, device=device
|
||||
)
|
||||
pxy = torch.tensor([xf, yf], dtype=torch.float32, device=device)
|
||||
|
||||
bary = barycentric_coordinates(pxy, v0[:2], v1[:2], v2[:2])
|
||||
if perspective_correct:
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
@ -123,8 +124,5 @@ class MeshRasterizer(nn.Module):
|
||||
perspective_correct=raster_settings.perspective_correct,
|
||||
)
|
||||
return Fragments(
|
||||
pix_to_face=pix_to_face,
|
||||
zbuf=zbuf,
|
||||
bary_coords=bary_coords,
|
||||
dists=dists,
|
||||
pix_to_face=pix_to_face, zbuf=zbuf, bary_coords=bary_coords, dists=dists
|
||||
)
|
||||
|
@ -7,6 +7,7 @@ import torch.nn as nn
|
||||
from .rasterizer import Fragments
|
||||
from .utils import _clip_barycentric_coordinates, _interpolate_zbuf
|
||||
|
||||
|
||||
# A renderer class should be initialized with a
|
||||
# function for rasterization and a function for shading.
|
||||
# The rasterizer should:
|
||||
@ -48,16 +49,12 @@ class MeshRenderer(nn.Module):
|
||||
the range for the corresponding face.
|
||||
"""
|
||||
fragments = self.rasterizer(meshes_world, **kwargs)
|
||||
raster_settings = kwargs.get(
|
||||
"raster_settings", self.rasterizer.raster_settings
|
||||
)
|
||||
raster_settings = kwargs.get("raster_settings", self.rasterizer.raster_settings)
|
||||
if raster_settings.blur_radius > 0.0:
|
||||
# TODO: potentially move barycentric clipping to the rasterizer
|
||||
# if no downstream functions requires unclipped values.
|
||||
# This will avoid unnecssary re-interpolation of the z buffer.
|
||||
clipped_bary_coords = _clip_barycentric_coordinates(
|
||||
fragments.bary_coords
|
||||
)
|
||||
clipped_bary_coords = _clip_barycentric_coordinates(fragments.bary_coords)
|
||||
clipped_zbuf = _interpolate_zbuf(
|
||||
fragments.pix_to_face, clipped_bary_coords, meshes_world
|
||||
)
|
||||
|
@ -16,6 +16,7 @@ from ..materials import Materials
|
||||
from .shading import flat_shading, gouraud_shading, phong_shading
|
||||
from .texturing import interpolate_texture_map, interpolate_vertex_colors
|
||||
|
||||
|
||||
# A Shader should take as input fragments from the output of rasterization
|
||||
# along with scene params and output images. A shader could perform operations
|
||||
# such as:
|
||||
@ -41,16 +42,12 @@ class HardPhongShader(nn.Module):
|
||||
|
||||
def __init__(self, device="cpu", cameras=None, lights=None, materials=None):
|
||||
super().__init__()
|
||||
self.lights = (
|
||||
lights if lights is not None else PointLights(device=device)
|
||||
)
|
||||
self.lights = lights if lights is not None else PointLights(device=device)
|
||||
self.materials = (
|
||||
materials if materials is not None else Materials(device=device)
|
||||
)
|
||||
self.cameras = (
|
||||
cameras
|
||||
if cameras is not None
|
||||
else OpenGLPerspectiveCameras(device=device)
|
||||
cameras if cameras is not None else OpenGLPerspectiveCameras(device=device)
|
||||
)
|
||||
|
||||
def forward(self, fragments, meshes, **kwargs) -> torch.Tensor:
|
||||
@ -85,28 +82,17 @@ class SoftPhongShader(nn.Module):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device="cpu",
|
||||
cameras=None,
|
||||
lights=None,
|
||||
materials=None,
|
||||
blend_params=None,
|
||||
self, device="cpu", cameras=None, lights=None, materials=None, blend_params=None
|
||||
):
|
||||
super().__init__()
|
||||
self.lights = (
|
||||
lights if lights is not None else PointLights(device=device)
|
||||
)
|
||||
self.lights = lights if lights is not None else PointLights(device=device)
|
||||
self.materials = (
|
||||
materials if materials is not None else Materials(device=device)
|
||||
)
|
||||
self.cameras = (
|
||||
cameras
|
||||
if cameras is not None
|
||||
else OpenGLPerspectiveCameras(device=device)
|
||||
)
|
||||
self.blend_params = (
|
||||
blend_params if blend_params is not None else BlendParams()
|
||||
cameras if cameras is not None else OpenGLPerspectiveCameras(device=device)
|
||||
)
|
||||
self.blend_params = blend_params if blend_params is not None else BlendParams()
|
||||
|
||||
def forward(self, fragments, meshes, **kwargs) -> torch.Tensor:
|
||||
texels = interpolate_vertex_colors(fragments, meshes)
|
||||
@ -142,16 +128,12 @@ class HardGouraudShader(nn.Module):
|
||||
|
||||
def __init__(self, device="cpu", cameras=None, lights=None, materials=None):
|
||||
super().__init__()
|
||||
self.lights = (
|
||||
lights if lights is not None else PointLights(device=device)
|
||||
)
|
||||
self.lights = lights if lights is not None else PointLights(device=device)
|
||||
self.materials = (
|
||||
materials if materials is not None else Materials(device=device)
|
||||
)
|
||||
self.cameras = (
|
||||
cameras
|
||||
if cameras is not None
|
||||
else OpenGLPerspectiveCameras(device=device)
|
||||
cameras if cameras is not None else OpenGLPerspectiveCameras(device=device)
|
||||
)
|
||||
|
||||
def forward(self, fragments, meshes, **kwargs) -> torch.Tensor:
|
||||
@ -185,28 +167,17 @@ class SoftGouraudShader(nn.Module):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device="cpu",
|
||||
cameras=None,
|
||||
lights=None,
|
||||
materials=None,
|
||||
blend_params=None,
|
||||
self, device="cpu", cameras=None, lights=None, materials=None, blend_params=None
|
||||
):
|
||||
super().__init__()
|
||||
self.lights = (
|
||||
lights if lights is not None else PointLights(device=device)
|
||||
)
|
||||
self.lights = lights if lights is not None else PointLights(device=device)
|
||||
self.materials = (
|
||||
materials if materials is not None else Materials(device=device)
|
||||
)
|
||||
self.cameras = (
|
||||
cameras
|
||||
if cameras is not None
|
||||
else OpenGLPerspectiveCameras(device=device)
|
||||
)
|
||||
self.blend_params = (
|
||||
blend_params if blend_params is not None else BlendParams()
|
||||
cameras if cameras is not None else OpenGLPerspectiveCameras(device=device)
|
||||
)
|
||||
self.blend_params = blend_params if blend_params is not None else BlendParams()
|
||||
|
||||
def forward(self, fragments, meshes, **kwargs) -> torch.Tensor:
|
||||
cameras = kwargs.get("cameras", self.cameras)
|
||||
@ -241,28 +212,17 @@ class TexturedSoftPhongShader(nn.Module):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device="cpu",
|
||||
cameras=None,
|
||||
lights=None,
|
||||
materials=None,
|
||||
blend_params=None,
|
||||
self, device="cpu", cameras=None, lights=None, materials=None, blend_params=None
|
||||
):
|
||||
super().__init__()
|
||||
self.lights = (
|
||||
lights if lights is not None else PointLights(device=device)
|
||||
)
|
||||
self.lights = lights if lights is not None else PointLights(device=device)
|
||||
self.materials = (
|
||||
materials if materials is not None else Materials(device=device)
|
||||
)
|
||||
self.cameras = (
|
||||
cameras
|
||||
if cameras is not None
|
||||
else OpenGLPerspectiveCameras(device=device)
|
||||
)
|
||||
self.blend_params = (
|
||||
blend_params if blend_params is not None else BlendParams()
|
||||
cameras if cameras is not None else OpenGLPerspectiveCameras(device=device)
|
||||
)
|
||||
self.blend_params = blend_params if blend_params is not None else BlendParams()
|
||||
|
||||
def forward(self, fragments, meshes, **kwargs) -> torch.Tensor:
|
||||
texels = interpolate_texture_map(fragments, meshes)
|
||||
@ -298,16 +258,12 @@ class HardFlatShader(nn.Module):
|
||||
|
||||
def __init__(self, device="cpu", cameras=None, lights=None, materials=None):
|
||||
super().__init__()
|
||||
self.lights = (
|
||||
lights if lights is not None else PointLights(device=device)
|
||||
)
|
||||
self.lights = lights if lights is not None else PointLights(device=device)
|
||||
self.materials = (
|
||||
materials if materials is not None else Materials(device=device)
|
||||
)
|
||||
self.cameras = (
|
||||
cameras
|
||||
if cameras is not None
|
||||
else OpenGLPerspectiveCameras(device=device)
|
||||
cameras if cameras is not None else OpenGLPerspectiveCameras(device=device)
|
||||
)
|
||||
|
||||
def forward(self, fragments, meshes, **kwargs) -> torch.Tensor:
|
||||
@ -346,9 +302,7 @@ class SoftSilhouetteShader(nn.Module):
|
||||
|
||||
def __init__(self, blend_params=None):
|
||||
super().__init__()
|
||||
self.blend_params = (
|
||||
blend_params if blend_params is not None else BlendParams()
|
||||
)
|
||||
self.blend_params = blend_params if blend_params is not None else BlendParams()
|
||||
|
||||
def forward(self, fragments, meshes, **kwargs) -> torch.Tensor:
|
||||
""""
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
import torch
|
||||
|
||||
from .texturing import interpolate_face_attributes
|
||||
@ -82,9 +83,7 @@ def phong_shading(
|
||||
return colors
|
||||
|
||||
|
||||
def gouraud_shading(
|
||||
meshes, fragments, lights, cameras, materials
|
||||
) -> torch.Tensor:
|
||||
def gouraud_shading(meshes, fragments, lights, cameras, materials) -> torch.Tensor:
|
||||
"""
|
||||
Apply per vertex shading. First compute the vertex illumination by applying
|
||||
ambient, diffuse and specular lighting. If vertex color is available,
|
||||
@ -131,9 +130,7 @@ def gouraud_shading(
|
||||
return colors
|
||||
|
||||
|
||||
def flat_shading(
|
||||
meshes, fragments, lights, cameras, materials, texels
|
||||
) -> torch.Tensor:
|
||||
def flat_shading(meshes, fragments, lights, cameras, materials, texels) -> torch.Tensor:
|
||||
"""
|
||||
Apply per face shading. Use the average face position and the face normals
|
||||
to compute the ambient, diffuse and specular lighting. Apply the ambient
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from pytorch3d.structures.textures import Textures
|
||||
|
||||
from .utils import interpolate_face_attributes
|
||||
@ -75,9 +74,7 @@ def interpolate_texture_map(fragments, meshes) -> torch.Tensor:
|
||||
# right-bottom pixel of input.
|
||||
|
||||
pixel_uvs = pixel_uvs * 2.0 - 1.0
|
||||
texture_maps = torch.flip(
|
||||
texture_maps, [2]
|
||||
) # flip y axis of the texture map
|
||||
texture_maps = torch.flip(texture_maps, [2]) # flip y axis of the texture map
|
||||
if texture_maps.device != pixel_uvs.device:
|
||||
texture_maps = texture_maps.to(pixel_uvs.device)
|
||||
texels = F.grid_sample(texture_maps, pixel_uvs, align_corners=False)
|
||||
@ -107,9 +104,7 @@ def interpolate_vertex_colors(fragments, meshes) -> torch.Tensor:
|
||||
There will be one C dimensional value for each element in
|
||||
fragments.pix_to_face.
|
||||
"""
|
||||
vertex_textures = meshes.textures.verts_rgb_padded().reshape(
|
||||
-1, 3
|
||||
) # (V, C)
|
||||
vertex_textures = meshes.textures.verts_rgb_padded().reshape(-1, 3) # (V, C)
|
||||
vertex_textures = vertex_textures[meshes.verts_padded_to_packed_idx(), :]
|
||||
faces_packed = meshes.faces_packed()
|
||||
faces_textures = vertex_textures[faces_packed] # (F, 3, C)
|
||||
|
@ -92,8 +92,6 @@ def _interpolate_zbuf(
|
||||
verts = meshes.verts_packed()
|
||||
faces = meshes.faces_packed()
|
||||
faces_verts_z = verts[faces][..., 2][..., None] # (F, 3, 1)
|
||||
return interpolate_face_attributes(
|
||||
pix_to_face, barycentric_coords, faces_verts_z
|
||||
)[
|
||||
return interpolate_face_attributes(pix_to_face, barycentric_coords, faces_verts_z)[
|
||||
..., 0
|
||||
] # (1, H, W, K)
|
||||
|
@ -5,4 +5,5 @@ from .rasterize_points import rasterize_points
|
||||
from .rasterizer import PointsRasterizationSettings, PointsRasterizer
|
||||
from .renderer import PointsRenderer
|
||||
|
||||
|
||||
__all__ = [k for k in globals().keys() if not k.startswith("_")]
|
||||
|
@ -5,6 +5,7 @@ import torch.nn as nn
|
||||
|
||||
from ..compositing import CompositeParams, alpha_composite, norm_weighted_sum
|
||||
|
||||
|
||||
# A compositor should take as input 3D points and some corresponding information.
|
||||
# Given this information, the compositor can:
|
||||
# - blend colors across the top K vertices at a pixel
|
||||
@ -19,15 +20,11 @@ class AlphaCompositor(nn.Module):
|
||||
super().__init__()
|
||||
|
||||
self.composite_params = (
|
||||
composite_params
|
||||
if composite_params is not None
|
||||
else CompositeParams()
|
||||
composite_params if composite_params is not None else CompositeParams()
|
||||
)
|
||||
|
||||
def forward(self, fragments, alphas, ptclds, **kwargs) -> torch.Tensor:
|
||||
images = alpha_composite(
|
||||
fragments, alphas, ptclds, self.composite_params
|
||||
)
|
||||
images = alpha_composite(fragments, alphas, ptclds, self.composite_params)
|
||||
return images
|
||||
|
||||
|
||||
@ -39,13 +36,9 @@ class NormWeightedCompositor(nn.Module):
|
||||
def __init__(self, composite_params=None):
|
||||
super().__init__()
|
||||
self.composite_params = (
|
||||
composite_params
|
||||
if composite_params is not None
|
||||
else CompositeParams()
|
||||
composite_params if composite_params is not None else CompositeParams()
|
||||
)
|
||||
|
||||
def forward(self, fragments, alphas, ptclds, **kwargs) -> torch.Tensor:
|
||||
images = norm_weighted_sum(
|
||||
fragments, alphas, ptclds, self.composite_params
|
||||
)
|
||||
images = norm_weighted_sum(fragments, alphas, ptclds, self.composite_params)
|
||||
return images
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from typing import Optional
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d import _C
|
||||
from pytorch3d.renderer.mesh.rasterize_meshes import pix_to_ndc
|
||||
|
||||
@ -155,10 +155,7 @@ class _RasterizePoints(torch.autograd.Function):
|
||||
|
||||
|
||||
def rasterize_points_python(
|
||||
pointclouds,
|
||||
image_size: int = 256,
|
||||
radius: float = 0.01,
|
||||
points_per_pixel: int = 8,
|
||||
pointclouds, image_size: int = 256, radius: float = 0.01, points_per_pixel: int = 8
|
||||
):
|
||||
"""
|
||||
Naive pure PyTorch implementation of pointcloud rasterization.
|
||||
@ -177,9 +174,7 @@ def rasterize_points_python(
|
||||
point_idxs = torch.full(
|
||||
(N, S, S, K), fill_value=-1, dtype=torch.int32, device=device
|
||||
)
|
||||
zbuf = torch.full(
|
||||
(N, S, S, K), fill_value=-1, dtype=torch.float32, device=device
|
||||
)
|
||||
zbuf = torch.full((N, S, S, K), fill_value=-1, dtype=torch.float32, device=device)
|
||||
pix_dists = torch.full(
|
||||
(N, S, S, K), fill_value=-1, dtype=torch.float32, device=device
|
||||
)
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
# A renderer class should be initialized with a
|
||||
# function for rasterization and a function for compositing.
|
||||
# The rasterizer should:
|
||||
|
@ -1,9 +1,10 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
|
||||
import numpy as np
|
||||
import warnings
|
||||
from typing import Any, Union
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
@ -45,10 +46,7 @@ class TensorAccessor(object):
|
||||
# Convert the attribute to a tensor if it is not a tensor.
|
||||
if not torch.is_tensor(value):
|
||||
value = torch.tensor(
|
||||
value,
|
||||
device=v.device,
|
||||
dtype=v.dtype,
|
||||
requires_grad=v.requires_grad,
|
||||
value, device=v.device, dtype=v.dtype, requires_grad=v.requires_grad
|
||||
)
|
||||
|
||||
# Check the shapes match the existing shape and the shape of the index.
|
||||
@ -253,9 +251,7 @@ class TensorProperties(object):
|
||||
return self
|
||||
|
||||
|
||||
def format_tensor(
|
||||
input, dtype=torch.float32, device: str = "cpu"
|
||||
) -> torch.Tensor:
|
||||
def format_tensor(input, dtype=torch.float32, device: str = "cpu") -> torch.Tensor:
|
||||
"""
|
||||
Helper function for converting a scalar value to a tensor.
|
||||
|
||||
@ -276,9 +272,7 @@ def format_tensor(
|
||||
return input
|
||||
|
||||
|
||||
def convert_to_tensors_and_broadcast(
|
||||
*args, dtype=torch.float32, device: str = "cpu"
|
||||
):
|
||||
def convert_to_tensors_and_broadcast(*args, dtype=torch.float32, device: str = "cpu"):
|
||||
"""
|
||||
Helper function to handle parsing an arbitrary number of inputs (*args)
|
||||
which all need to have the same batch dimension.
|
||||
|
@ -3,11 +3,7 @@
|
||||
from .meshes import Meshes, join_meshes
|
||||
from .pointclouds import Pointclouds
|
||||
from .textures import Textures
|
||||
from .utils import (
|
||||
list_to_packed,
|
||||
list_to_padded,
|
||||
packed_to_list,
|
||||
padded_to_list,
|
||||
)
|
||||
from .utils import list_to_packed, list_to_padded, packed_to_list, padded_to_list
|
||||
|
||||
|
||||
__all__ = [k for k in globals().keys() if not k.startswith("_")]
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
|
||||
from . import utils as struct_utils
|
||||
@ -314,14 +315,11 @@ class Meshes(object):
|
||||
if isinstance(verts, list) and isinstance(faces, list):
|
||||
self._verts_list = verts
|
||||
self._faces_list = [
|
||||
f[f.gt(-1).all(1)].to(torch.int64) if len(f) > 0 else f
|
||||
for f in faces
|
||||
f[f.gt(-1).all(1)].to(torch.int64) if len(f) > 0 else f for f in faces
|
||||
]
|
||||
self._N = len(self._verts_list)
|
||||
self.device = torch.device("cpu")
|
||||
self.valid = torch.zeros(
|
||||
(self._N,), dtype=torch.bool, device=self.device
|
||||
)
|
||||
self.valid = torch.zeros((self._N,), dtype=torch.bool, device=self.device)
|
||||
if self._N > 0:
|
||||
self.device = self._verts_list[0].device
|
||||
self._num_verts_per_mesh = torch.tensor(
|
||||
@ -348,18 +346,14 @@ class Meshes(object):
|
||||
|
||||
elif torch.is_tensor(verts) and torch.is_tensor(faces):
|
||||
if verts.size(2) != 3 and faces.size(2) != 3:
|
||||
raise ValueError(
|
||||
"Verts and Faces tensors have incorrect dimensions."
|
||||
)
|
||||
raise ValueError("Verts and Faces tensors have incorrect dimensions.")
|
||||
self._verts_padded = verts
|
||||
self._faces_padded = faces.to(torch.int64)
|
||||
self._N = self._verts_padded.shape[0]
|
||||
self._V = self._verts_padded.shape[1]
|
||||
|
||||
self.device = self._verts_padded.device
|
||||
self.valid = torch.zeros(
|
||||
(self._N,), dtype=torch.bool, device=self.device
|
||||
)
|
||||
self.valid = torch.zeros((self._N,), dtype=torch.bool, device=self.device)
|
||||
if self._N > 0:
|
||||
# Check that padded faces - which have value -1 - are at the
|
||||
# end of the tensors
|
||||
@ -400,12 +394,8 @@ class Meshes(object):
|
||||
|
||||
# Set the num verts/faces on the textures if present.
|
||||
if self.textures is not None:
|
||||
self.textures._num_faces_per_mesh = (
|
||||
self._num_faces_per_mesh.tolist()
|
||||
)
|
||||
self.textures._num_verts_per_mesh = (
|
||||
self._num_verts_per_mesh.tolist()
|
||||
)
|
||||
self.textures._num_faces_per_mesh = self._num_faces_per_mesh.tolist()
|
||||
self.textures._num_verts_per_mesh = self._num_verts_per_mesh.tolist()
|
||||
|
||||
def __len__(self):
|
||||
return self._N
|
||||
@ -665,8 +655,7 @@ class Meshes(object):
|
||||
|
||||
self._verts_padded_to_packed_idx = torch.cat(
|
||||
[
|
||||
torch.arange(v, dtype=torch.int64, device=self.device)
|
||||
+ i * self._V
|
||||
torch.arange(v, dtype=torch.int64, device=self.device) + i * self._V
|
||||
for (i, v) in enumerate(self._num_verts_per_mesh)
|
||||
],
|
||||
dim=0,
|
||||
@ -706,15 +695,10 @@ class Meshes(object):
|
||||
tensor of normals of shape (N, max(V_n), 3).
|
||||
"""
|
||||
if self.isempty():
|
||||
return torch.zeros(
|
||||
(self._N, 0, 3), dtype=torch.float32, device=self.device
|
||||
)
|
||||
return torch.zeros((self._N, 0, 3), dtype=torch.float32, device=self.device)
|
||||
verts_normals_list = self.verts_normals_list()
|
||||
return struct_utils.list_to_padded(
|
||||
verts_normals_list,
|
||||
(self._V, 3),
|
||||
pad_value=0.0,
|
||||
equisized=self.equisized,
|
||||
verts_normals_list, (self._V, 3), pad_value=0.0, equisized=self.equisized
|
||||
)
|
||||
|
||||
def faces_normals_packed(self):
|
||||
@ -750,15 +734,10 @@ class Meshes(object):
|
||||
tensor of normals of shape (N, max(F_n), 3).
|
||||
"""
|
||||
if self.isempty():
|
||||
return torch.zeros(
|
||||
(self._N, 0, 3), dtype=torch.float32, device=self.device
|
||||
)
|
||||
return torch.zeros((self._N, 0, 3), dtype=torch.float32, device=self.device)
|
||||
faces_normals_list = self.faces_normals_list()
|
||||
return struct_utils.list_to_padded(
|
||||
faces_normals_list,
|
||||
(self._F, 3),
|
||||
pad_value=0.0,
|
||||
equisized=self.equisized,
|
||||
faces_normals_list, (self._F, 3), pad_value=0.0, equisized=self.equisized
|
||||
)
|
||||
|
||||
def faces_areas_packed(self):
|
||||
@ -797,9 +776,7 @@ class Meshes(object):
|
||||
return
|
||||
faces_packed = self.faces_packed()
|
||||
verts_packed = self.verts_packed()
|
||||
face_areas, face_normals = mesh_face_areas_normals(
|
||||
verts_packed, faces_packed
|
||||
)
|
||||
face_areas, face_normals = mesh_face_areas_normals(verts_packed, faces_packed)
|
||||
self._faces_areas_packed = face_areas
|
||||
self._faces_normals_packed = face_normals
|
||||
|
||||
@ -813,9 +790,7 @@ class Meshes(object):
|
||||
refresh: Set to True to force recomputation of vertex normals.
|
||||
Default: False.
|
||||
"""
|
||||
if not (
|
||||
refresh or any(v is None for v in [self._verts_normals_packed])
|
||||
):
|
||||
if not (refresh or any(v is None for v in [self._verts_normals_packed])):
|
||||
return
|
||||
|
||||
if self.isempty():
|
||||
@ -867,8 +842,7 @@ class Meshes(object):
|
||||
Computes the padded version of meshes from verts_list and faces_list.
|
||||
"""
|
||||
if not (
|
||||
refresh
|
||||
or any(v is None for v in [self._verts_padded, self._faces_padded])
|
||||
refresh or any(v is None for v in [self._verts_padded, self._faces_padded])
|
||||
):
|
||||
return
|
||||
|
||||
@ -887,16 +861,10 @@ class Meshes(object):
|
||||
)
|
||||
else:
|
||||
self._faces_padded = struct_utils.list_to_padded(
|
||||
faces_list,
|
||||
(self._F, 3),
|
||||
pad_value=-1.0,
|
||||
equisized=self.equisized,
|
||||
faces_list, (self._F, 3), pad_value=-1.0, equisized=self.equisized
|
||||
)
|
||||
self._verts_padded = struct_utils.list_to_padded(
|
||||
verts_list,
|
||||
(self._V, 3),
|
||||
pad_value=0.0,
|
||||
equisized=self.equisized,
|
||||
verts_list, (self._V, 3), pad_value=0.0, equisized=self.equisized
|
||||
)
|
||||
|
||||
# TODO(nikhilar) Improve performance of _compute_packed.
|
||||
@ -1055,9 +1023,7 @@ class Meshes(object):
|
||||
face_to_edge = inverse_idxs[face_to_edge]
|
||||
self._faces_packed_to_edges_packed = face_to_edge
|
||||
|
||||
num_edges_per_mesh = torch.zeros(
|
||||
self._N, dtype=torch.int32, device=self.device
|
||||
)
|
||||
num_edges_per_mesh = torch.zeros(self._N, dtype=torch.int32, device=self.device)
|
||||
ones = torch.ones(1, dtype=torch.int32, device=self.device).expand(
|
||||
self._edges_packed_to_mesh_idx.shape
|
||||
)
|
||||
|
@ -176,17 +176,13 @@ class Pointclouds(object):
|
||||
self._points_list = points
|
||||
self._N = len(self._points_list)
|
||||
self.device = torch.device("cpu")
|
||||
self.valid = torch.zeros(
|
||||
(self._N,), dtype=torch.bool, device=self.device
|
||||
)
|
||||
self.valid = torch.zeros((self._N,), dtype=torch.bool, device=self.device)
|
||||
self._num_points_per_cloud = []
|
||||
|
||||
if self._N > 0:
|
||||
for p in self._points_list:
|
||||
if len(p) > 0 and (p.dim() != 2 or p.shape[1] != 3):
|
||||
raise ValueError(
|
||||
"Clouds in list must be of shape Px3 or empty"
|
||||
)
|
||||
raise ValueError("Clouds in list must be of shape Px3 or empty")
|
||||
|
||||
self.device = self._points_list[0].device
|
||||
num_points_per_cloud = torch.tensor(
|
||||
@ -210,9 +206,7 @@ class Pointclouds(object):
|
||||
self._N = self._points_padded.shape[0]
|
||||
self._P = self._points_padded.shape[1]
|
||||
self.device = self._points_padded.device
|
||||
self.valid = torch.ones(
|
||||
(self._N,), dtype=torch.bool, device=self.device
|
||||
)
|
||||
self.valid = torch.ones((self._N,), dtype=torch.bool, device=self.device)
|
||||
self._num_points_per_cloud = torch.tensor(
|
||||
[self._P] * self._N, device=self.device
|
||||
)
|
||||
@ -260,9 +254,7 @@ class Pointclouds(object):
|
||||
|
||||
if isinstance(aux_input, list):
|
||||
if len(aux_input) != self._N:
|
||||
raise ValueError(
|
||||
"Points and auxiliary input must be the same length."
|
||||
)
|
||||
raise ValueError("Points and auxiliary input must be the same length.")
|
||||
for p, d in zip(self._num_points_per_cloud, aux_input):
|
||||
if p != d.shape[0]:
|
||||
raise ValueError(
|
||||
@ -282,9 +274,7 @@ class Pointclouds(object):
|
||||
return aux_input, None, aux_input_C
|
||||
elif torch.is_tensor(aux_input):
|
||||
if aux_input.dim() != 3:
|
||||
raise ValueError(
|
||||
"Auxiliary input tensor has incorrect dimensions."
|
||||
)
|
||||
raise ValueError("Auxiliary input tensor has incorrect dimensions.")
|
||||
if self._N != aux_input.shape[0]:
|
||||
raise ValueError("Points and inputs must be the same length.")
|
||||
if self._P != aux_input.shape[1]:
|
||||
@ -531,8 +521,7 @@ class Pointclouds(object):
|
||||
else:
|
||||
self._padded_to_packed_idx = torch.cat(
|
||||
[
|
||||
torch.arange(v, dtype=torch.int64, device=self.device)
|
||||
+ i * self._P
|
||||
torch.arange(v, dtype=torch.int64, device=self.device) + i * self._P
|
||||
for (i, v) in enumerate(self._num_points_per_cloud)
|
||||
],
|
||||
dim=0,
|
||||
@ -551,9 +540,7 @@ class Pointclouds(object):
|
||||
|
||||
self._normals_padded, self._features_padded = None, None
|
||||
if self.isempty():
|
||||
self._points_padded = torch.zeros(
|
||||
(self._N, 0, 3), device=self.device
|
||||
)
|
||||
self._points_padded = torch.zeros((self._N, 0, 3), device=self.device)
|
||||
else:
|
||||
self._points_padded = struct_utils.list_to_padded(
|
||||
self.points_list(),
|
||||
@ -621,9 +608,7 @@ class Pointclouds(object):
|
||||
|
||||
points_list_to_packed = struct_utils.list_to_packed(points_list)
|
||||
self._points_packed = points_list_to_packed[0]
|
||||
if not torch.allclose(
|
||||
self._num_points_per_cloud, points_list_to_packed[1]
|
||||
):
|
||||
if not torch.allclose(self._num_points_per_cloud, points_list_to_packed[1]):
|
||||
raise ValueError("Inconsistent list to packed conversion")
|
||||
self._cloud_to_packed_first_idx = points_list_to_packed[2]
|
||||
self._packed_to_cloud_idx = points_list_to_packed[3]
|
||||
@ -696,13 +681,9 @@ class Pointclouds(object):
|
||||
if other._N > 0:
|
||||
other._points_list = [v.to(device) for v in other.points_list()]
|
||||
if other._normals_list is not None:
|
||||
other._normals_list = [
|
||||
n.to(device) for n in other.normals_list()
|
||||
]
|
||||
other._normals_list = [n.to(device) for n in other.normals_list()]
|
||||
if other._features_list is not None:
|
||||
other._features_list = [
|
||||
f.to(device) for f in other.features_list()
|
||||
]
|
||||
other._features_list = [f.to(device) for f in other.features_list()]
|
||||
for k in self._INTERNAL_TENSORS:
|
||||
v = getattr(self, k)
|
||||
if torch.is_tensor(v):
|
||||
@ -892,16 +873,11 @@ class Pointclouds(object):
|
||||
for features in self.features_list():
|
||||
new_features_list.extend(features.clone() for _ in range(N))
|
||||
return Pointclouds(
|
||||
points=new_points_list,
|
||||
normals=new_normals_list,
|
||||
features=new_features_list,
|
||||
points=new_points_list, normals=new_normals_list, features=new_features_list
|
||||
)
|
||||
|
||||
def update_padded(
|
||||
self,
|
||||
new_points_padded,
|
||||
new_normals_padded=None,
|
||||
new_features_padded=None,
|
||||
self, new_points_padded, new_normals_padded=None, new_features_padded=None
|
||||
):
|
||||
"""
|
||||
Returns a Pointcloud structure with updated padded tensors and copies of
|
||||
@ -920,13 +896,9 @@ class Pointclouds(object):
|
||||
|
||||
def check_shapes(x, size):
|
||||
if x.shape[0] != size[0]:
|
||||
raise ValueError(
|
||||
"new values must have the same batch dimension."
|
||||
)
|
||||
raise ValueError("new values must have the same batch dimension.")
|
||||
if x.shape[1] != size[1]:
|
||||
raise ValueError(
|
||||
"new values must have the same number of points."
|
||||
)
|
||||
raise ValueError("new values must have the same number of points.")
|
||||
if size[2] is not None:
|
||||
if x.shape[2] != size[2]:
|
||||
raise ValueError(
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import torch
|
||||
import torchvision.transforms as T
|
||||
|
||||
@ -233,11 +234,7 @@ class Textures(object):
|
||||
|
||||
if all(
|
||||
v is not None
|
||||
for v in [
|
||||
self._faces_uvs_padded,
|
||||
self._verts_uvs_padded,
|
||||
self._maps_padded,
|
||||
]
|
||||
for v in [self._faces_uvs_padded, self._verts_uvs_padded, self._maps_padded]
|
||||
):
|
||||
new_verts_uvs = _extend_tensor(self._verts_uvs_padded, N)
|
||||
new_faces_uvs = _extend_tensor(self._faces_uvs_padded, N)
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from typing import List, Union
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
@ -38,9 +39,7 @@ def list_to_padded(
|
||||
pad_dim1 = max(y.shape[1] for y in x if len(y) > 0)
|
||||
else:
|
||||
if len(pad_size) != 2:
|
||||
raise ValueError(
|
||||
"Pad size must contain target size for 1st and 2nd dim"
|
||||
)
|
||||
raise ValueError("Pad size must contain target size for 1st and 2nd dim")
|
||||
pad_dim0, pad_dim1 = pad_size
|
||||
|
||||
N = len(x)
|
||||
@ -55,9 +54,7 @@ def list_to_padded(
|
||||
return x_padded
|
||||
|
||||
|
||||
def padded_to_list(
|
||||
x: torch.Tensor, split_size: Union[list, tuple, None] = None
|
||||
):
|
||||
def padded_to_list(x: torch.Tensor, split_size: Union[list, tuple, None] = None):
|
||||
r"""
|
||||
Transforms a padded tensor of shape (N, M, K) into a list of N tensors
|
||||
of shape (Mi, Ki) where (Mi, Ki) is specified in split_size(i), or of shape
|
||||
@ -81,9 +78,7 @@ def padded_to_list(
|
||||
|
||||
N = len(split_size)
|
||||
if x.shape[0] != N:
|
||||
raise ValueError(
|
||||
"Split size must be of same length as inputs first dimension"
|
||||
)
|
||||
raise ValueError("Split size must be of same length as inputs first dimension")
|
||||
|
||||
for i in range(N):
|
||||
if isinstance(split_size[i], int):
|
||||
@ -119,9 +114,7 @@ def list_to_packed(x: List[torch.Tensor]):
|
||||
"""
|
||||
N = len(x)
|
||||
num_items = torch.zeros(N, dtype=torch.int64, device=x[0].device)
|
||||
item_packed_first_idx = torch.zeros(
|
||||
N, dtype=torch.int64, device=x[0].device
|
||||
)
|
||||
item_packed_first_idx = torch.zeros(N, dtype=torch.int64, device=x[0].device)
|
||||
item_packed_to_list_idx = []
|
||||
cur = 0
|
||||
for i, y in enumerate(x):
|
||||
@ -187,9 +180,7 @@ def padded_to_packed(
|
||||
N, M, D = x.shape
|
||||
|
||||
if split_size is not None and pad_value is not None:
|
||||
raise ValueError(
|
||||
"Only one of split_size or pad_value should be provided."
|
||||
)
|
||||
raise ValueError("Only one of split_size or pad_value should be provided.")
|
||||
|
||||
x_packed = x.reshape(-1, D) # flatten padded
|
||||
|
||||
@ -205,9 +196,7 @@ def padded_to_packed(
|
||||
# Convert to packed using split sizes
|
||||
N = len(split_size)
|
||||
if x.shape[0] != N:
|
||||
raise ValueError(
|
||||
"Split size must be of same length as inputs first dimension"
|
||||
)
|
||||
raise ValueError("Split size must be of same length as inputs first dimension")
|
||||
|
||||
if not all(isinstance(i, int) for i in split_size):
|
||||
raise ValueError(
|
||||
|
@ -22,4 +22,5 @@ from .so3 import (
|
||||
)
|
||||
from .transform3d import Rotate, RotateAxisAngle, Scale, Transform3d, Translate
|
||||
|
||||
|
||||
__all__ = [k for k in globals().keys() if not k.startswith("_")]
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import functools
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
@ -155,9 +156,7 @@ def euler_angles_to_matrix(euler_angles, convention: str):
|
||||
for letter in convention:
|
||||
if letter not in ("X", "Y", "Z"):
|
||||
raise ValueError(f"Invalid letter {letter} in convention string.")
|
||||
matrices = map(
|
||||
_axis_angle_rotation, convention, torch.unbind(euler_angles, -1)
|
||||
)
|
||||
matrices = map(_axis_angle_rotation, convention, torch.unbind(euler_angles, -1))
|
||||
return functools.reduce(torch.matmul, matrices)
|
||||
|
||||
|
||||
@ -246,10 +245,7 @@ def matrix_to_euler_angles(matrix, convention: str):
|
||||
|
||||
|
||||
def random_quaternions(
|
||||
n: int,
|
||||
dtype: Optional[torch.dtype] = None,
|
||||
device=None,
|
||||
requires_grad=False,
|
||||
n: int, dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate random quaternions representing rotations,
|
||||
@ -266,19 +262,14 @@ def random_quaternions(
|
||||
Returns:
|
||||
Quaternions as tensor of shape (N, 4).
|
||||
"""
|
||||
o = torch.randn(
|
||||
(n, 4), dtype=dtype, device=device, requires_grad=requires_grad
|
||||
)
|
||||
o = torch.randn((n, 4), dtype=dtype, device=device, requires_grad=requires_grad)
|
||||
s = (o * o).sum(1)
|
||||
o = o / _copysign(torch.sqrt(s), o[:, 0])[:, None]
|
||||
return o
|
||||
|
||||
|
||||
def random_rotations(
|
||||
n: int,
|
||||
dtype: Optional[torch.dtype] = None,
|
||||
device=None,
|
||||
requires_grad=False,
|
||||
n: int, dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate random rotations as 3x3 rotation matrices.
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
HAT_INV_SKEW_SYMMETRIC_TOL = 1e-5
|
||||
|
||||
|
||||
@ -65,9 +66,7 @@ def so3_rotation_angle(R, eps: float = 1e-4, cos_angle: bool = False):
|
||||
rot_trace = R[:, 0, 0] + R[:, 1, 1] + R[:, 2, 2]
|
||||
|
||||
if ((rot_trace < -1.0 - eps) + (rot_trace > 3.0 + eps)).any():
|
||||
raise ValueError(
|
||||
"A matrix has trace outside valid range [-1-eps,3+eps]."
|
||||
)
|
||||
raise ValueError("A matrix has trace outside valid range [-1-eps,3+eps].")
|
||||
|
||||
# clamp to valid range
|
||||
rot_trace = torch.clamp(rot_trace, -1.0, 3.0)
|
||||
|
@ -3,6 +3,7 @@
|
||||
import math
|
||||
import warnings
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
from .rotation_conversions import _axis_angle_rotation
|
||||
@ -230,9 +231,7 @@ class Transform3d:
|
||||
# the transformations with get_matrix(), this correctly
|
||||
# right-multiplies by the inverse of self._matrix
|
||||
# at the end of the composition.
|
||||
tinv._transforms = [
|
||||
t.inverse() for t in reversed(self._transforms)
|
||||
]
|
||||
tinv._transforms = [t.inverse() for t in reversed(self._transforms)]
|
||||
last = Transform3d(device=self.device)
|
||||
last._matrix = i_matrix
|
||||
tinv._transforms.append(last)
|
||||
@ -334,9 +333,7 @@ class Transform3d:
|
||||
return self.compose(Scale(device=self.device, *args, **kwargs))
|
||||
|
||||
def rotate_axis_angle(self, *args, **kwargs):
|
||||
return self.compose(
|
||||
RotateAxisAngle(device=self.device, *args, **kwargs)
|
||||
)
|
||||
return self.compose(RotateAxisAngle(device=self.device, *args, **kwargs))
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
@ -388,9 +385,7 @@ class Transform3d:
|
||||
|
||||
|
||||
class Translate(Transform3d):
|
||||
def __init__(
|
||||
self, x, y=None, z=None, dtype=torch.float32, device: str = "cpu"
|
||||
):
|
||||
def __init__(self, x, y=None, z=None, dtype=torch.float32, device: str = "cpu"):
|
||||
"""
|
||||
Create a new Transform3d representing 3D translations.
|
||||
|
||||
@ -424,9 +419,7 @@ class Translate(Transform3d):
|
||||
|
||||
|
||||
class Scale(Transform3d):
|
||||
def __init__(
|
||||
self, x, y=None, z=None, dtype=torch.float32, device: str = "cpu"
|
||||
):
|
||||
def __init__(self, x, y=None, z=None, dtype=torch.float32, device: str = "cpu"):
|
||||
"""
|
||||
A Transform3d representing a scaling operation, with different scale
|
||||
factors along each coordinate axis.
|
||||
@ -444,9 +437,7 @@ class Scale(Transform3d):
|
||||
- 1D torch tensor
|
||||
"""
|
||||
super().__init__(device=device)
|
||||
xyz = _handle_input(
|
||||
x, y, z, dtype, device, "scale", allow_singleton=True
|
||||
)
|
||||
xyz = _handle_input(x, y, z, dtype, device, "scale", allow_singleton=True)
|
||||
N = xyz.shape[0]
|
||||
|
||||
# TODO: Can we do this all in one go somehow?
|
||||
@ -469,11 +460,7 @@ class Scale(Transform3d):
|
||||
|
||||
class Rotate(Transform3d):
|
||||
def __init__(
|
||||
self,
|
||||
R,
|
||||
dtype=torch.float32,
|
||||
device: str = "cpu",
|
||||
orthogonal_tol: float = 1e-5,
|
||||
self, R, dtype=torch.float32, device: str = "cpu", orthogonal_tol: float = 1e-5
|
||||
):
|
||||
"""
|
||||
Create a new Transform3d representing 3D rotation using a rotation
|
||||
@ -562,9 +549,7 @@ def _handle_coord(c, dtype, device):
|
||||
return c
|
||||
|
||||
|
||||
def _handle_input(
|
||||
x, y, z, dtype, device, name: str, allow_singleton: bool = False
|
||||
):
|
||||
def _handle_input(x, y, z, dtype, device, name: str, allow_singleton: bool = False):
|
||||
"""
|
||||
Helper function to handle parsing logic for building transforms. The output
|
||||
is always a tensor of shape (N, 3), but there are several types of allowed
|
||||
|
@ -3,4 +3,5 @@
|
||||
from .ico_sphere import ico_sphere
|
||||
from .torus import torus
|
||||
|
||||
|
||||
__all__ = [k for k in globals().keys() if not k.startswith("_")]
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
|
||||
import torch
|
||||
|
||||
from pytorch3d.ops.subdivide_meshes import SubdivideMeshes
|
||||
from pytorch3d.structures.meshes import Meshes
|
||||
|
||||
|
||||
# Vertex coordinates for a level 0 ico-sphere.
|
||||
_ico_verts0 = [
|
||||
[-0.5257, 0.8507, 0.0000],
|
||||
|
@ -3,8 +3,8 @@
|
||||
from itertools import tee
|
||||
from math import cos, pi, sin
|
||||
from typing import Iterator, Optional, Tuple
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d.structures.meshes import Meshes
|
||||
|
||||
|
||||
@ -16,11 +16,7 @@ def _make_pair_range(N: int) -> Iterator[Tuple[int, int]]:
|
||||
|
||||
|
||||
def torus(
|
||||
r: float,
|
||||
R: float,
|
||||
sides: int,
|
||||
rings: int,
|
||||
device: Optional[torch.device] = None,
|
||||
r: float, R: float, sides: int, rings: int, device: Optional[torch.device] = None
|
||||
) -> Meshes:
|
||||
"""
|
||||
Create vertices and faces for a torus.
|
||||
|
@ -4,10 +4,12 @@
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
|
||||
import nbformat
|
||||
from bs4 import BeautifulSoup
|
||||
from nbconvert import HTMLExporter, ScriptExporter
|
||||
|
||||
|
||||
TEMPLATE = """const CWD = process.cwd();
|
||||
|
||||
const React = require('react');
|
||||
@ -41,9 +43,7 @@ def gen_tutorials(repo_dir: str) -> None:
|
||||
Also create ipynb and py versions of tutorial in Docusaurus site for
|
||||
download.
|
||||
"""
|
||||
with open(
|
||||
os.path.join(repo_dir, "website", "tutorials.json"), "r"
|
||||
) as infile:
|
||||
with open(os.path.join(repo_dir, "website", "tutorials.json"), "r") as infile:
|
||||
tutorial_config = json.loads(infile.read())
|
||||
|
||||
tutorial_ids = {x["id"] for v in tutorial_config.values() for x in v}
|
||||
@ -107,10 +107,7 @@ if __name__ == "__main__":
|
||||
description="Generate JS, HTML, ipynb, and py files for tutorials."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--repo_dir",
|
||||
metavar="path",
|
||||
required=True,
|
||||
help="PyTorch3D repo directory.",
|
||||
"--repo_dir", metavar="path", required=True, help="PyTorch3D repo directory."
|
||||
)
|
||||
args = parser.parse_args()
|
||||
gen_tutorials(args.repo_dir)
|
||||
|
3
setup.py
3
setup.py
@ -3,8 +3,9 @@
|
||||
|
||||
import glob
|
||||
import os
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
import torch
|
||||
from setuptools import find_packages, setup
|
||||
from torch.utils.cpp_extension import CUDA_HOME, CppExtension, CUDAExtension
|
||||
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from fvcore.common.benchmark import benchmark
|
||||
from test_blending import TestBlending
|
||||
|
||||
|
||||
@ -18,12 +18,7 @@ def bm_blending() -> None:
|
||||
for case in test_cases:
|
||||
n, s, k, d = case
|
||||
kwargs_list.append(
|
||||
{
|
||||
"num_meshes": n,
|
||||
"image_size": s,
|
||||
"faces_per_pixel": k,
|
||||
"device": d,
|
||||
}
|
||||
{"num_meshes": n, "image_size": s, "faces_per_pixel": k, "device": d}
|
||||
)
|
||||
|
||||
benchmark(
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_chamfer import TestChamfer
|
||||
|
||||
|
||||
@ -25,9 +24,4 @@ def bm_chamfer() -> None:
|
||||
{"batch_size": 1, "P1": 1000, "P2": 3000, "return_normals": False},
|
||||
{"batch_size": 1, "P1": 1000, "P2": 30000, "return_normals": True},
|
||||
]
|
||||
benchmark(
|
||||
TestChamfer.chamfer_with_init,
|
||||
"CHAMFER",
|
||||
kwargs_list,
|
||||
warmup_iters=1,
|
||||
)
|
||||
benchmark(TestChamfer.chamfer_with_init, "CHAMFER", kwargs_list, warmup_iters=1)
|
||||
|
@ -1,7 +1,6 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_cubify import TestCubify
|
||||
|
||||
|
||||
@ -11,6 +10,4 @@ def bm_cubify() -> None:
|
||||
{"batch_size": 64, "V": 16},
|
||||
{"batch_size": 16, "V": 32},
|
||||
]
|
||||
benchmark(
|
||||
TestCubify.cubify_with_init, "CUBIFY", kwargs_list, warmup_iters=1
|
||||
)
|
||||
benchmark(TestCubify.cubify_with_init, "CUBIFY", kwargs_list, warmup_iters=1)
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_face_areas_normals import TestFaceAreasNormals
|
||||
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_graph_conv import TestGraphConv
|
||||
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from pytorch3d import _C
|
||||
from pytorch3d.ops.knn import _knn_points_idx_naive
|
||||
|
||||
@ -32,9 +32,7 @@ def benchmark_knn_cuda_versions() -> None:
|
||||
knn_kwargs.append({"N": N, "D": D, "P": P, "K": K, "v": version})
|
||||
for N, P, D in product(Ns, Ps, Ds):
|
||||
nn_kwargs.append({"N": N, "D": D, "P": P})
|
||||
benchmark(
|
||||
knn_cuda_with_init, "KNN_CUDA_VERSIONS", knn_kwargs, warmup_iters=1
|
||||
)
|
||||
benchmark(knn_cuda_with_init, "KNN_CUDA_VERSIONS", knn_kwargs, warmup_iters=1)
|
||||
benchmark(nn_cuda_with_init, "NN_CUDA", nn_kwargs, warmup_iters=1)
|
||||
|
||||
|
||||
@ -50,10 +48,7 @@ def benchmark_knn_cuda_vs_naive() -> None:
|
||||
if P <= 4096:
|
||||
naive_kwargs.append({"N": N, "D": D, "P": P, "K": K})
|
||||
benchmark(
|
||||
knn_python_cuda_with_init,
|
||||
"KNN_CUDA_PYTHON",
|
||||
naive_kwargs,
|
||||
warmup_iters=1,
|
||||
knn_python_cuda_with_init, "KNN_CUDA_PYTHON", naive_kwargs, warmup_iters=1
|
||||
)
|
||||
benchmark(knn_cuda_with_init, "KNN_CUDA", knn_kwargs, warmup_iters=1)
|
||||
|
||||
@ -68,9 +63,7 @@ def benchmark_knn_cpu() -> None:
|
||||
knn_kwargs.append({"N": N, "D": D, "P": P, "K": K})
|
||||
for N, P, D in product(Ns, Ps, Ds):
|
||||
nn_kwargs.append({"N": N, "D": D, "P": P})
|
||||
benchmark(
|
||||
knn_python_cpu_with_init, "KNN_CPU_PYTHON", knn_kwargs, warmup_iters=1
|
||||
)
|
||||
benchmark(knn_python_cpu_with_init, "KNN_CPU_PYTHON", knn_kwargs, warmup_iters=1)
|
||||
benchmark(knn_cpu_with_init, "KNN_CPU_CPP", knn_kwargs, warmup_iters=1)
|
||||
benchmark(nn_cpu_with_init, "NN_CPU_CPP", nn_kwargs, warmup_iters=1)
|
||||
|
||||
|
@ -5,6 +5,7 @@ import glob
|
||||
import importlib
|
||||
from os.path import basename, dirname, isfile, join, sys
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# pyre-ignore[16]
|
||||
if len(sys.argv) > 1:
|
||||
@ -25,7 +26,5 @@ if __name__ == "__main__":
|
||||
for attr in dir(module):
|
||||
# Run all the functions with names "bm_*" in the module.
|
||||
if attr.startswith("bm_"):
|
||||
print(
|
||||
"Running benchmarks for " + module_name + "/" + attr + "..."
|
||||
)
|
||||
print("Running benchmarks for " + module_name + "/" + attr + "...")
|
||||
getattr(module, attr)()
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from fvcore.common.benchmark import benchmark
|
||||
from test_mesh_edge_loss import TestMeshEdgeLoss
|
||||
|
||||
|
||||
@ -17,8 +17,5 @@ def bm_mesh_edge_loss() -> None:
|
||||
n, v, f = case
|
||||
kwargs_list.append({"num_meshes": n, "max_v": v, "max_f": f})
|
||||
benchmark(
|
||||
TestMeshEdgeLoss.mesh_edge_loss,
|
||||
"MESH_EDGE_LOSS",
|
||||
kwargs_list,
|
||||
warmup_iters=1,
|
||||
TestMeshEdgeLoss.mesh_edge_loss, "MESH_EDGE_LOSS", kwargs_list, warmup_iters=1
|
||||
)
|
||||
|
@ -1,7 +1,6 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_obj_io import TestMeshObjIO
|
||||
from test_ply_io import TestMeshPlyIO
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_mesh_laplacian_smoothing import TestLaplacianSmoothing
|
||||
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_mesh_normal_consistency import TestMeshNormalConsistency
|
||||
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_meshes import TestMeshes
|
||||
|
||||
|
||||
@ -20,9 +20,7 @@ def bm_compute_packed_padded_meshes() -> None:
|
||||
test_cases = product(num_meshes, max_v, max_f, devices)
|
||||
for case in test_cases:
|
||||
n, v, f, d = case
|
||||
kwargs_list.append(
|
||||
{"num_meshes": n, "max_v": v, "max_f": f, "device": d}
|
||||
)
|
||||
kwargs_list.append({"num_meshes": n, "max_v": v, "max_f": f, "device": d})
|
||||
benchmark(
|
||||
TestMeshes.compute_packed_with_init,
|
||||
"COMPUTE_PACKED",
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_nearest_neighbor_points import TestNearestNeighborPoints
|
||||
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_packed_to_padded import TestPackedToPadded
|
||||
|
||||
|
||||
@ -23,13 +23,7 @@ def bm_packed_to_padded() -> None:
|
||||
for case in test_cases:
|
||||
n, v, f, d, b = case
|
||||
kwargs_list.append(
|
||||
{
|
||||
"num_meshes": n,
|
||||
"num_verts": v,
|
||||
"num_faces": f,
|
||||
"num_d": d,
|
||||
"device": b,
|
||||
}
|
||||
{"num_meshes": n, "num_verts": v, "num_faces": f, "num_d": d, "device": b}
|
||||
)
|
||||
benchmark(
|
||||
TestPackedToPadded.packed_to_padded_with_init,
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from fvcore.common.benchmark import benchmark
|
||||
from test_pointclouds import TestPointclouds
|
||||
|
||||
|
||||
|
@ -2,11 +2,12 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_rasterize_meshes import TestRasterizeMeshes
|
||||
|
||||
|
||||
# ico levels:
|
||||
# 0: (12 verts, 20 faces)
|
||||
# 1: (42 verts, 80 faces)
|
||||
@ -39,12 +40,7 @@ def bm_rasterize_meshes() -> None:
|
||||
for case in test_cases:
|
||||
n, ic, im, b = case
|
||||
kwargs_list.append(
|
||||
{
|
||||
"num_meshes": n,
|
||||
"ico_level": ic,
|
||||
"image_size": im,
|
||||
"blur_radius": b,
|
||||
}
|
||||
{"num_meshes": n, "ico_level": ic, "image_size": im, "blur_radius": b}
|
||||
)
|
||||
benchmark(
|
||||
TestRasterizeMeshes.rasterize_meshes_cpu_with_init,
|
||||
@ -63,9 +59,7 @@ def bm_rasterize_meshes() -> None:
|
||||
test_cases = product(num_meshes, ico_level, image_size, blur, bin_size)
|
||||
# only keep cases where bin_size == 0 or image_size / bin_size < 16
|
||||
test_cases = [
|
||||
elem
|
||||
for elem in test_cases
|
||||
if (elem[-1] == 0 or elem[-3] / elem[-1] < 16)
|
||||
elem for elem in test_cases if (elem[-1] == 0 or elem[-3] / elem[-1] < 16)
|
||||
]
|
||||
for case in test_cases:
|
||||
n, ic, im, b, bn = case
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from pytorch3d.renderer.points.rasterize_points import (
|
||||
rasterize_points,
|
||||
rasterize_points_python,
|
||||
@ -40,9 +39,7 @@ def bm_python_vs_cpu() -> None:
|
||||
{"N": 1, "P": 32, "img_size": 32, "radius": 0.1, "pts_per_pxl": 3},
|
||||
{"N": 2, "P": 32, "img_size": 32, "radius": 0.1, "pts_per_pxl": 3},
|
||||
]
|
||||
benchmark(
|
||||
_bm_python_with_init, "RASTERIZE_PYTHON", kwargs_list, warmup_iters=1
|
||||
)
|
||||
benchmark(_bm_python_with_init, "RASTERIZE_PYTHON", kwargs_list, warmup_iters=1)
|
||||
benchmark(_bm_cpu_with_init, "RASTERIZE_CPU", kwargs_list, warmup_iters=1)
|
||||
kwargs_list = [
|
||||
{"N": 2, "P": 32, "img_size": 32, "radius": 0.1, "pts_per_pxl": 3},
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_sample_points_from_meshes import TestSamplePoints
|
||||
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_so3 import TestSO3
|
||||
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from fvcore.common.benchmark import benchmark
|
||||
from test_subdivide_meshes import TestSubdivideMeshes
|
||||
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
|
||||
from itertools import product
|
||||
|
||||
import torch
|
||||
from fvcore.common.benchmark import benchmark
|
||||
|
||||
from test_vert_align import TestVertAlign
|
||||
|
||||
|
||||
@ -25,8 +25,5 @@ def bm_vert_align() -> None:
|
||||
)
|
||||
|
||||
benchmark(
|
||||
TestVertAlign.vert_align_with_init,
|
||||
"VERT_ALIGN",
|
||||
kwargs_list,
|
||||
warmup_iters=1,
|
||||
TestVertAlign.vert_align_with_init, "VERT_ALIGN", kwargs_list, warmup_iters=1
|
||||
)
|
||||
|
@ -1,8 +1,9 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
@ -11,17 +12,13 @@ class TestCaseMixin(unittest.TestCase):
|
||||
"""
|
||||
Verify that tensor1 and tensor2 have their data in distinct locations.
|
||||
"""
|
||||
self.assertNotEqual(
|
||||
tensor1.storage().data_ptr(), tensor2.storage().data_ptr()
|
||||
)
|
||||
self.assertNotEqual(tensor1.storage().data_ptr(), tensor2.storage().data_ptr())
|
||||
|
||||
def assertNotSeparate(self, tensor1, tensor2) -> None:
|
||||
"""
|
||||
Verify that tensor1 and tensor2 have their data in the same locations.
|
||||
"""
|
||||
self.assertEqual(
|
||||
tensor1.storage().data_ptr(), tensor2.storage().data_ptr()
|
||||
)
|
||||
self.assertEqual(tensor1.storage().data_ptr(), tensor2.storage().data_ptr())
|
||||
|
||||
def assertAllSeparate(self, tensor_list) -> None:
|
||||
"""
|
||||
@ -57,7 +54,5 @@ class TestCaseMixin(unittest.TestCase):
|
||||
input, other, rtol=rtol, atol=atol, equal_nan=equal_nan
|
||||
)
|
||||
else:
|
||||
close = np.allclose(
|
||||
input, other, rtol=rtol, atol=atol, equal_nan=equal_nan
|
||||
)
|
||||
close = np.allclose(input, other, rtol=rtol, atol=atol, equal_nan=equal_nan)
|
||||
self.assertTrue(close)
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from pytorch3d.renderer.blending import (
|
||||
BlendParams,
|
||||
hard_rgb_blend,
|
||||
@ -43,9 +43,7 @@ def sigmoid_blend_naive_loop(colors, fragments, blend_params):
|
||||
return pixel_colors
|
||||
|
||||
|
||||
def sigmoid_blend_naive_loop_backward(
|
||||
grad_images, images, fragments, blend_params
|
||||
):
|
||||
def sigmoid_blend_naive_loop_backward(grad_images, images, fragments, blend_params):
|
||||
pix_to_face = fragments.pix_to_face
|
||||
dists = fragments.dists
|
||||
sigma = blend_params.sigma
|
||||
@ -135,14 +133,7 @@ class TestBlending(unittest.TestCase):
|
||||
torch.manual_seed(42)
|
||||
|
||||
def _compare_impls(
|
||||
self,
|
||||
fn1,
|
||||
fn2,
|
||||
args1,
|
||||
args2,
|
||||
grad_var1=None,
|
||||
grad_var2=None,
|
||||
compare_grads=True,
|
||||
self, fn1, fn2, args1, args2, grad_var1=None, grad_var2=None, compare_grads=True
|
||||
):
|
||||
|
||||
out1 = fn1(*args1)
|
||||
@ -160,9 +151,7 @@ class TestBlending(unittest.TestCase):
|
||||
(out2 * grad_out).sum().backward()
|
||||
self.assertTrue(hasattr(grad_var2, "grad"))
|
||||
self.assertTrue(
|
||||
torch.allclose(
|
||||
grad_var1.grad.cpu(), grad_var2.grad.cpu(), atol=2e-5
|
||||
)
|
||||
torch.allclose(grad_var1.grad.cpu(), grad_var2.grad.cpu(), atol=2e-5)
|
||||
)
|
||||
|
||||
def test_hard_rgb_blend(self):
|
||||
@ -199,9 +188,7 @@ class TestBlending(unittest.TestCase):
|
||||
# # (-) means inside triangle, (+) means outside triangle.
|
||||
random_sign_flip = torch.rand((N, S, S, K))
|
||||
random_sign_flip[random_sign_flip > 0.5] *= -1.0
|
||||
dists = torch.randn(
|
||||
size=(N, S, S, K), requires_grad=True, device=device
|
||||
)
|
||||
dists = torch.randn(size=(N, S, S, K), requires_grad=True, device=device)
|
||||
fragments = Fragments(
|
||||
pix_to_face=pix_to_face,
|
||||
bary_coords=empty, # dummy
|
||||
@ -238,9 +225,7 @@ class TestBlending(unittest.TestCase):
|
||||
# # (-) means inside triangle, (+) means outside triangle.
|
||||
random_sign_flip = torch.rand((N, S, S, K))
|
||||
random_sign_flip[random_sign_flip > 0.5] *= -1.0
|
||||
dists1 = torch.randn(
|
||||
size=(N, S, S, K), requires_grad=True, device=device
|
||||
)
|
||||
dists1 = torch.randn(size=(N, S, S, K), requires_grad=True, device=device)
|
||||
dists2 = dists1.detach().clone()
|
||||
dists2.requires_grad = True
|
||||
|
||||
@ -276,9 +261,7 @@ class TestBlending(unittest.TestCase):
|
||||
# of the image with surrounding padded values.
|
||||
N, S, K = 1, 8, 2
|
||||
device = torch.device("cuda")
|
||||
pix_to_face = -torch.ones(
|
||||
(N, S, S, K), dtype=torch.int64, device=device
|
||||
)
|
||||
pix_to_face = -torch.ones((N, S, S, K), dtype=torch.int64, device=device)
|
||||
h = int(S / 2)
|
||||
pix_to_face_full = torch.randint(
|
||||
size=(N, h, h, K), low=0, high=100, device=device
|
||||
@ -294,9 +277,7 @@ class TestBlending(unittest.TestCase):
|
||||
|
||||
# randomly flip the sign of the distance
|
||||
# (-) means inside triangle, (+) means outside triangle.
|
||||
dists1 = (
|
||||
torch.randn(size=(N, S, S, K), device=device) * random_sign_flip
|
||||
)
|
||||
dists1 = torch.randn(size=(N, S, S, K), device=device) * random_sign_flip
|
||||
dists2 = dists1.clone()
|
||||
zbuf2 = zbuf1.clone()
|
||||
dists1.requires_grad = True
|
||||
@ -353,9 +334,7 @@ class TestBlending(unittest.TestCase):
|
||||
# # (-) means inside triangle, (+) means outside triangle.
|
||||
random_sign_flip = torch.rand((N, S, S, K), device=device)
|
||||
random_sign_flip[random_sign_flip > 0.5] *= -1.0
|
||||
dists1 = torch.randn(
|
||||
size=(N, S, S, K), requires_grad=True, device=device
|
||||
)
|
||||
dists1 = torch.randn(size=(N, S, S, K), requires_grad=True, device=device)
|
||||
fragments = Fragments(
|
||||
pix_to_face=pix_to_face,
|
||||
bary_coords=empty, # dummy
|
||||
@ -398,15 +377,10 @@ class TestBlending(unittest.TestCase):
|
||||
# # (-) means inside triangle, (+) means outside triangle.
|
||||
random_sign_flip = torch.rand((N, S, S, K), device=device)
|
||||
random_sign_flip[random_sign_flip > 0.5] *= -1.0
|
||||
dists1 = torch.randn(
|
||||
size=(N, S, S, K), requires_grad=True, device=device
|
||||
)
|
||||
dists1 = torch.randn(size=(N, S, S, K), requires_grad=True, device=device)
|
||||
zbuf = torch.randn(size=(N, S, S, K), requires_grad=True, device=device)
|
||||
fragments = Fragments(
|
||||
pix_to_face=pix_to_face,
|
||||
bary_coords=empty, # dummy
|
||||
zbuf=zbuf,
|
||||
dists=dists1,
|
||||
pix_to_face=pix_to_face, bary_coords=empty, zbuf=zbuf, dists=dists1 # dummy
|
||||
)
|
||||
blend_params = BlendParams(sigma=1e-3)
|
||||
|
||||
|
@ -3,6 +3,7 @@ import unittest
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# This file groups together tests which look at the code without running it.
|
||||
|
||||
|
||||
@ -61,6 +62,5 @@ class TestBuild(unittest.TestCase):
|
||||
if firstline.startswith(("# -*-", "#!")):
|
||||
firstline = f.readline()
|
||||
self.assertTrue(
|
||||
firstline.endswith(expect),
|
||||
f"{i} missing copyright header.",
|
||||
firstline.endswith(expect), f"{i} missing copyright header."
|
||||
)
|
||||
|
@ -26,10 +26,11 @@
|
||||
# SOFTWARE.
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.renderer.cameras import (
|
||||
OpenGLOrthographicCameras,
|
||||
OpenGLPerspectiveCameras,
|
||||
@ -43,8 +44,6 @@ from pytorch3d.renderer.cameras import (
|
||||
from pytorch3d.transforms import Transform3d
|
||||
from pytorch3d.transforms.so3 import so3_exponential_map
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
|
||||
|
||||
# Naive function adapted from SoftRasterizer for test purposes.
|
||||
def perspective_project_naive(points, fov=60.0):
|
||||
@ -58,9 +57,7 @@ def perspective_project_naive(points, fov=60.0):
|
||||
coordinate (no z renormalization)
|
||||
"""
|
||||
device = points.device
|
||||
halfFov = torch.tensor(
|
||||
(fov / 2) / 180 * np.pi, dtype=torch.float32, device=device
|
||||
)
|
||||
halfFov = torch.tensor((fov / 2) / 180 * np.pi, dtype=torch.float32, device=device)
|
||||
scale = torch.tan(halfFov[None])
|
||||
scale = scale[:, None]
|
||||
z = points[:, :, 2]
|
||||
@ -150,9 +147,9 @@ class TestCameraHelpers(TestCaseMixin, unittest.TestCase):
|
||||
dist = 2.7
|
||||
elev = 90.0
|
||||
azim = 0.0
|
||||
expected_position = torch.tensor(
|
||||
[0.0, 2.7, 0.0], dtype=torch.float32
|
||||
).view(1, 3)
|
||||
expected_position = torch.tensor([0.0, 2.7, 0.0], dtype=torch.float32).view(
|
||||
1, 3
|
||||
)
|
||||
position = camera_position_from_spherical_angles(dist, elev, azim)
|
||||
self.assertClose(position, expected_position, atol=2e-7)
|
||||
|
||||
@ -171,9 +168,9 @@ class TestCameraHelpers(TestCaseMixin, unittest.TestCase):
|
||||
dist = torch.tensor(2.7)
|
||||
elev = torch.tensor(0.0)
|
||||
azim = torch.tensor(90.0)
|
||||
expected_position = torch.tensor(
|
||||
[2.7, 0.0, 0.0], dtype=torch.float32
|
||||
).view(1, 3)
|
||||
expected_position = torch.tensor([2.7, 0.0, 0.0], dtype=torch.float32).view(
|
||||
1, 3
|
||||
)
|
||||
position = camera_position_from_spherical_angles(dist, elev, azim)
|
||||
self.assertClose(position, expected_position, atol=2e-7)
|
||||
|
||||
@ -181,9 +178,9 @@ class TestCameraHelpers(TestCaseMixin, unittest.TestCase):
|
||||
dist = 2.7
|
||||
elev = torch.tensor(0.0)
|
||||
azim = 90.0
|
||||
expected_position = torch.tensor(
|
||||
[2.7, 0.0, 0.0], dtype=torch.float32
|
||||
).view(1, 3)
|
||||
expected_position = torch.tensor([2.7, 0.0, 0.0], dtype=torch.float32).view(
|
||||
1, 3
|
||||
)
|
||||
position = camera_position_from_spherical_angles(dist, elev, azim)
|
||||
self.assertClose(position, expected_position, atol=2e-7)
|
||||
|
||||
@ -228,8 +225,7 @@ class TestCameraHelpers(TestCaseMixin, unittest.TestCase):
|
||||
elev = torch.tensor([0.0])
|
||||
azim = torch.tensor([90.0])
|
||||
expected_position = torch.tensor(
|
||||
[[2.0, 0.0, 0.0], [3.0, 0.0, 0.0], [5.0, 0.0, 0.0]],
|
||||
dtype=torch.float32,
|
||||
[[2.0, 0.0, 0.0], [3.0, 0.0, 0.0], [5.0, 0.0, 0.0]], dtype=torch.float32
|
||||
)
|
||||
position = camera_position_from_spherical_angles(dist, elev, azim)
|
||||
self.assertClose(position, expected_position, atol=3e-7)
|
||||
@ -239,8 +235,7 @@ class TestCameraHelpers(TestCaseMixin, unittest.TestCase):
|
||||
elev = 0.0
|
||||
azim = torch.tensor(90.0)
|
||||
expected_position = torch.tensor(
|
||||
[[2.0, 0.0, 0.0], [3.0, 0.0, 0.0], [5.0, 0.0, 0.0]],
|
||||
dtype=torch.float32,
|
||||
[[2.0, 0.0, 0.0], [3.0, 0.0, 0.0], [5.0, 0.0, 0.0]], dtype=torch.float32
|
||||
)
|
||||
position = camera_position_from_spherical_angles(dist, elev, azim)
|
||||
self.assertClose(position, expected_position, atol=3e-7)
|
||||
@ -364,9 +359,7 @@ class TestCameraHelpers(TestCaseMixin, unittest.TestCase):
|
||||
):
|
||||
cam = cam_type(R=R, T=T)
|
||||
RT_class = cam.get_world_to_view_transform()
|
||||
self.assertTrue(
|
||||
torch.allclose(RT.get_matrix(), RT_class.get_matrix())
|
||||
)
|
||||
self.assertTrue(torch.allclose(RT.get_matrix(), RT_class.get_matrix()))
|
||||
|
||||
self.assertTrue(isinstance(RT, Transform3d))
|
||||
|
||||
@ -539,9 +532,7 @@ class TestOpenGLOrthographicProjection(TestCaseMixin, unittest.TestCase):
|
||||
# applying the scale puts the z coordinate at the far clipping plane
|
||||
# so the z is mapped to 1.0
|
||||
projected_verts = torch.tensor([2, 1, 1], dtype=torch.float32)
|
||||
cameras = OpenGLOrthographicCameras(
|
||||
znear=1.0, zfar=10.0, scale_xyz=scale
|
||||
)
|
||||
cameras = OpenGLOrthographicCameras(znear=1.0, zfar=10.0, scale_xyz=scale)
|
||||
P = cameras.get_projection_transform()
|
||||
v1 = P.transform_points(vertices)
|
||||
v2 = orthographic_project_naive(vertices, scale)
|
||||
@ -578,9 +569,7 @@ class TestOpenGLOrthographicProjection(TestCaseMixin, unittest.TestCase):
|
||||
far = torch.tensor([10.0])
|
||||
near = 1.0
|
||||
scale = torch.tensor([[1.0, 1.0, 1.0]], requires_grad=True)
|
||||
cameras = OpenGLOrthographicCameras(
|
||||
znear=near, zfar=far, scale_xyz=scale
|
||||
)
|
||||
cameras = OpenGLOrthographicCameras(znear=near, zfar=far, scale_xyz=scale)
|
||||
P = cameras.get_projection_transform()
|
||||
vertices = torch.tensor([1.0, 2.0, 10.0], dtype=torch.float32)
|
||||
vertices_batch = vertices[None, None, :]
|
||||
@ -683,15 +672,11 @@ class TestSfMPerspectiveProjection(TestCaseMixin, unittest.TestCase):
|
||||
self.assertClose(v3[..., :2], v2[..., :2])
|
||||
|
||||
def test_perspective_kwargs(self):
|
||||
cameras = SfMPerspectiveCameras(
|
||||
focal_length=5.0, principal_point=((2.5, 2.5),)
|
||||
)
|
||||
cameras = SfMPerspectiveCameras(focal_length=5.0, principal_point=((2.5, 2.5),))
|
||||
P = cameras.get_projection_transform(
|
||||
focal_length=2.0, principal_point=((2.5, 3.5),)
|
||||
)
|
||||
vertices = torch.randn([3, 4, 3], dtype=torch.float32)
|
||||
v1 = P.transform_points(vertices)
|
||||
v2 = sfm_perspective_project_naive(
|
||||
vertices, fx=2.0, fy=2.0, p0x=2.5, p0y=3.5
|
||||
)
|
||||
v2 = sfm_perspective_project_naive(vertices, fx=2.0, fy=2.0, p0x=2.5, p0y=3.5)
|
||||
self.assertClose(v1, v2)
|
||||
|
@ -1,12 +1,11 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from pytorch3d.loss import chamfer_distance
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.loss import chamfer_distance
|
||||
|
||||
|
||||
class TestChamfer(TestCaseMixin, unittest.TestCase):
|
||||
@ -19,14 +18,10 @@ class TestChamfer(TestCaseMixin, unittest.TestCase):
|
||||
"""
|
||||
device = torch.device("cuda:0")
|
||||
p1 = torch.rand((batch_size, P1, 3), dtype=torch.float32, device=device)
|
||||
p1_normals = torch.rand(
|
||||
(batch_size, P1, 3), dtype=torch.float32, device=device
|
||||
)
|
||||
p1_normals = torch.rand((batch_size, P1, 3), dtype=torch.float32, device=device)
|
||||
p1_normals = p1_normals / p1_normals.norm(dim=2, p=2, keepdim=True)
|
||||
p2 = torch.rand((batch_size, P2, 3), dtype=torch.float32, device=device)
|
||||
p2_normals = torch.rand(
|
||||
(batch_size, P2, 3), dtype=torch.float32, device=device
|
||||
)
|
||||
p2_normals = torch.rand((batch_size, P2, 3), dtype=torch.float32, device=device)
|
||||
p2_normals = p2_normals / p2_normals.norm(dim=2, p=2, keepdim=True)
|
||||
weights = torch.rand((batch_size,), dtype=torch.float32, device=device)
|
||||
|
||||
@ -47,9 +42,7 @@ class TestChamfer(TestCaseMixin, unittest.TestCase):
|
||||
for n in range(N):
|
||||
for i1 in range(P1):
|
||||
for i2 in range(P2):
|
||||
dist[n, i1, i2] = torch.sum(
|
||||
(p1[n, i1, :] - p2[n, i2, :]) ** 2
|
||||
)
|
||||
dist[n, i1, i2] = torch.sum((p1[n, i1, :] - p2[n, i2, :]) ** 2)
|
||||
|
||||
loss = [
|
||||
torch.min(dist, dim=2)[0], # (N, P1)
|
||||
@ -146,11 +139,7 @@ class TestChamfer(TestCaseMixin, unittest.TestCase):
|
||||
# Error when point_reduction = "none" and batch_reduction = "none".
|
||||
with self.assertRaises(ValueError):
|
||||
chamfer_distance(
|
||||
p1,
|
||||
p2,
|
||||
weights=weights,
|
||||
batch_reduction="none",
|
||||
point_reduction="none",
|
||||
p1, p2, weights=weights, batch_reduction="none", point_reduction="none"
|
||||
)
|
||||
|
||||
# Error when batch_reduction is not in ["none", "mean", "sum"].
|
||||
@ -339,9 +328,7 @@ class TestChamfer(TestCaseMixin, unittest.TestCase):
|
||||
loss, loss_norm = chamfer_distance(p1, p2, weights=weights)
|
||||
|
||||
@staticmethod
|
||||
def chamfer_with_init(
|
||||
batch_size: int, P1: int, P2: int, return_normals: bool
|
||||
):
|
||||
def chamfer_with_init(batch_size: int, P1: int, P2: int, return_normals: bool):
|
||||
p1, p2, p1_normals, p2_normals, weights = TestChamfer.init_pointclouds(
|
||||
batch_size, P1, P2
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d.renderer.compositing import (
|
||||
alpha_composite,
|
||||
norm_weighted_sum,
|
||||
@ -37,9 +37,7 @@ class TestAccumulatePoints(unittest.TestCase):
|
||||
continue
|
||||
|
||||
alpha = alphas[b, k, j, i]
|
||||
output[b, c, j, i] += (
|
||||
features[c, n_idx] * alpha * t_alpha
|
||||
)
|
||||
output[b, c, j, i] += features[c, n_idx] * alpha * t_alpha
|
||||
t_alpha = (1 - alpha) * t_alpha
|
||||
|
||||
return output
|
||||
@ -105,17 +103,13 @@ class TestAccumulatePoints(unittest.TestCase):
|
||||
continue
|
||||
|
||||
alpha = alphas[b, k, j, i]
|
||||
output[b, c, j, i] += (
|
||||
features[c, n_idx] * alpha / t_alpha
|
||||
)
|
||||
output[b, c, j, i] += features[c, n_idx] * alpha / t_alpha
|
||||
|
||||
return output
|
||||
|
||||
def test_python(self):
|
||||
device = torch.device("cpu")
|
||||
self._simple_alphacomposite(
|
||||
self.accumulate_alphacomposite_python, device
|
||||
)
|
||||
self._simple_alphacomposite(self.accumulate_alphacomposite_python, device)
|
||||
self._simple_wsum(self.accumulate_weightedsum_python, device)
|
||||
self._simple_wsumnorm(self.accumulate_weightedsumnorm_python, device)
|
||||
|
||||
@ -138,9 +132,7 @@ class TestAccumulatePoints(unittest.TestCase):
|
||||
self._python_vs_cpu_vs_cuda(
|
||||
self.accumulate_weightedsumnorm_python, norm_weighted_sum
|
||||
)
|
||||
self._python_vs_cpu_vs_cuda(
|
||||
self.accumulate_weightedsum_python, weighted_sum
|
||||
)
|
||||
self._python_vs_cpu_vs_cuda(self.accumulate_weightedsum_python, weighted_sum)
|
||||
|
||||
def _python_vs_cpu_vs_cuda(self, accumulate_func_python, accumulate_func):
|
||||
torch.manual_seed(231)
|
||||
@ -208,15 +200,11 @@ class TestAccumulatePoints(unittest.TestCase):
|
||||
grads2 = [gradsi.grad.data.clone().cpu() for gradsi in grads2]
|
||||
|
||||
for i in range(0, len(grads1)):
|
||||
self.assertTrue(
|
||||
torch.allclose(grads1[i].cpu(), grads2[i].cpu(), atol=1e-6)
|
||||
)
|
||||
self.assertTrue(torch.allclose(grads1[i].cpu(), grads2[i].cpu(), atol=1e-6))
|
||||
|
||||
def _simple_wsum(self, accum_func, device):
|
||||
# Initialise variables
|
||||
features = torch.Tensor(
|
||||
[[0.1, 0.4, 0.6, 0.9], [0.1, 0.4, 0.6, 0.9]]
|
||||
).to(device)
|
||||
features = torch.Tensor([[0.1, 0.4, 0.6, 0.9], [0.1, 0.4, 0.6, 0.9]]).to(device)
|
||||
|
||||
alphas = torch.Tensor(
|
||||
[
|
||||
@ -285,15 +273,11 @@ class TestAccumulatePoints(unittest.TestCase):
|
||||
]
|
||||
).to(device)
|
||||
|
||||
self.assertTrue(
|
||||
torch.allclose(result.cpu(), true_result.cpu(), rtol=1e-3)
|
||||
)
|
||||
self.assertTrue(torch.allclose(result.cpu(), true_result.cpu(), rtol=1e-3))
|
||||
|
||||
def _simple_wsumnorm(self, accum_func, device):
|
||||
# Initialise variables
|
||||
features = torch.Tensor(
|
||||
[[0.1, 0.4, 0.6, 0.9], [0.1, 0.4, 0.6, 0.9]]
|
||||
).to(device)
|
||||
features = torch.Tensor([[0.1, 0.4, 0.6, 0.9], [0.1, 0.4, 0.6, 0.9]]).to(device)
|
||||
|
||||
alphas = torch.Tensor(
|
||||
[
|
||||
@ -362,15 +346,11 @@ class TestAccumulatePoints(unittest.TestCase):
|
||||
]
|
||||
).to(device)
|
||||
|
||||
self.assertTrue(
|
||||
torch.allclose(result.cpu(), true_result.cpu(), rtol=1e-3)
|
||||
)
|
||||
self.assertTrue(torch.allclose(result.cpu(), true_result.cpu(), rtol=1e-3))
|
||||
|
||||
def _simple_alphacomposite(self, accum_func, device):
|
||||
# Initialise variables
|
||||
features = torch.Tensor(
|
||||
[[0.1, 0.4, 0.6, 0.9], [0.1, 0.4, 0.6, 0.9]]
|
||||
).to(device)
|
||||
features = torch.Tensor([[0.1, 0.4, 0.6, 0.9], [0.1, 0.4, 0.6, 0.9]]).to(device)
|
||||
|
||||
alphas = torch.Tensor(
|
||||
[
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d.ops import cubify
|
||||
|
||||
|
||||
@ -33,9 +33,7 @@ class TestCubify(unittest.TestCase):
|
||||
|
||||
# 1st-check
|
||||
verts, faces = meshes.get_mesh_verts_faces(0)
|
||||
self.assertTrue(
|
||||
torch.allclose(faces.max(), torch.tensor([verts.size(0) - 1]))
|
||||
)
|
||||
self.assertTrue(torch.allclose(faces.max(), torch.tensor([verts.size(0) - 1])))
|
||||
self.assertTrue(
|
||||
torch.allclose(
|
||||
verts,
|
||||
@ -80,9 +78,7 @@ class TestCubify(unittest.TestCase):
|
||||
)
|
||||
# 2nd-check
|
||||
verts, faces = meshes.get_mesh_verts_faces(1)
|
||||
self.assertTrue(
|
||||
torch.allclose(faces.max(), torch.tensor([verts.size(0) - 1]))
|
||||
)
|
||||
self.assertTrue(torch.allclose(faces.max(), torch.tensor([verts.size(0) - 1])))
|
||||
self.assertTrue(
|
||||
torch.allclose(
|
||||
verts,
|
||||
@ -275,9 +271,7 @@ class TestCubify(unittest.TestCase):
|
||||
@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
|
||||
)
|
||||
voxels = torch.rand((batch_size, V, V, V), dtype=torch.float32, device=device)
|
||||
torch.cuda.synchronize()
|
||||
|
||||
def convert():
|
||||
|
@ -2,13 +2,12 @@
|
||||
|
||||
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.ops import mesh_face_areas_normals
|
||||
from pytorch3d.structures.meshes import Meshes
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
|
||||
|
||||
class TestFaceAreasNormals(TestCaseMixin, unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
@ -27,10 +26,7 @@ class TestFaceAreasNormals(TestCaseMixin, unittest.TestCase):
|
||||
faces_list = []
|
||||
for _ in range(num_meshes):
|
||||
verts = torch.rand(
|
||||
(num_verts, 3),
|
||||
dtype=torch.float32,
|
||||
device=device,
|
||||
requires_grad=True,
|
||||
(num_verts, 3), dtype=torch.float32, device=device, requires_grad=True
|
||||
)
|
||||
faces = torch.randint(
|
||||
num_verts, size=(num_faces, 3), dtype=torch.int64, device=device
|
||||
@ -55,9 +51,7 @@ class TestFaceAreasNormals(TestCaseMixin, unittest.TestCase):
|
||||
v02 = vertices_faces[:, 2] - vertices_faces[:, 0]
|
||||
normals = torch.cross(v01, v02, dim=1) # (F, 3)
|
||||
face_areas = normals.norm(dim=-1) / 2
|
||||
face_normals = torch.nn.functional.normalize(
|
||||
normals, p=2, dim=1, eps=1e-6
|
||||
)
|
||||
face_normals = torch.nn.functional.normalize(normals, p=2, dim=1, eps=1e-6)
|
||||
return face_areas, face_normals
|
||||
|
||||
def _test_face_areas_normals_helper(self, device, dtype=torch.float32):
|
||||
@ -76,10 +70,7 @@ class TestFaceAreasNormals(TestCaseMixin, unittest.TestCase):
|
||||
verts_torch = verts.detach().clone().to(dtype)
|
||||
verts_torch.requires_grad = True
|
||||
faces_torch = faces.detach().clone()
|
||||
(
|
||||
areas_torch,
|
||||
normals_torch,
|
||||
) = TestFaceAreasNormals.face_areas_normals_python(
|
||||
(areas_torch, normals_torch) = TestFaceAreasNormals.face_areas_normals_python(
|
||||
verts_torch, faces_torch
|
||||
)
|
||||
self.assertClose(areas_torch, areas, atol=1e-7)
|
||||
|
@ -1,20 +1,15 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d import _C
|
||||
from pytorch3d.ops.graph_conv import (
|
||||
GraphConv,
|
||||
gather_scatter,
|
||||
gather_scatter_python,
|
||||
)
|
||||
from pytorch3d.ops.graph_conv import GraphConv, gather_scatter, gather_scatter_python
|
||||
from pytorch3d.structures.meshes import Meshes
|
||||
from pytorch3d.utils import ico_sphere
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
|
||||
|
||||
class TestGraphConv(TestCaseMixin, unittest.TestCase):
|
||||
def test_undirected(self):
|
||||
@ -89,8 +84,7 @@ class TestGraphConv(TestCaseMixin, unittest.TestCase):
|
||||
w1 = torch.tensor([[-1, -1, -1]], dtype=dtype)
|
||||
|
||||
expected_y = torch.tensor(
|
||||
[[1 + 2 + 3 - 4 - 5 - 6 - 7 - 8 - 9], [4 + 5 + 6], [7 + 8 + 9]],
|
||||
dtype=dtype,
|
||||
[[1 + 2 + 3 - 4 - 5 - 6 - 7 - 8 - 9], [4 + 5 + 6], [7 + 8 + 9]], dtype=dtype
|
||||
)
|
||||
|
||||
conv = GraphConv(3, 1, directed=True).to(dtype)
|
||||
@ -126,17 +120,13 @@ class TestGraphConv(TestCaseMixin, unittest.TestCase):
|
||||
def test_cpu_cuda_tensor_error(self):
|
||||
device = torch.device("cuda:0")
|
||||
verts = torch.tensor(
|
||||
[[1, 2, 3], [4, 5, 6], [7, 8, 9]],
|
||||
dtype=torch.float32,
|
||||
device=device,
|
||||
[[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=torch.float32, device=device
|
||||
)
|
||||
edges = torch.tensor([[0, 1], [0, 2]])
|
||||
conv = GraphConv(3, 1, directed=True).to(torch.float32)
|
||||
with self.assertRaises(Exception) as err:
|
||||
conv(verts, edges)
|
||||
self.assertTrue(
|
||||
"tensors must be on the same device." in str(err.exception)
|
||||
)
|
||||
self.assertTrue("tensors must be on the same device." in str(err.exception))
|
||||
|
||||
def test_gather_scatter(self):
|
||||
"""
|
||||
@ -178,12 +168,10 @@ class TestGraphConv(TestCaseMixin, unittest.TestCase):
|
||||
backend: str = "cuda",
|
||||
):
|
||||
device = torch.device("cuda") if backend == "cuda" else "cpu"
|
||||
verts_list = torch.tensor(
|
||||
num_verts * [[0.11, 0.22, 0.33]], device=device
|
||||
).view(-1, 3)
|
||||
faces_list = torch.tensor(num_faces * [[1, 2, 3]], device=device).view(
|
||||
verts_list = torch.tensor(num_verts * [[0.11, 0.22, 0.33]], device=device).view(
|
||||
-1, 3
|
||||
)
|
||||
faces_list = torch.tensor(num_faces * [[1, 2, 3]], device=device).view(-1, 3)
|
||||
meshes = Meshes(num_meshes * [verts_list], num_meshes * [faces_list])
|
||||
gconv = GraphConv(gconv_dim, gconv_dim, directed=directed)
|
||||
gconv.to(device)
|
||||
@ -191,9 +179,7 @@ class TestGraphConv(TestCaseMixin, unittest.TestCase):
|
||||
total_verts = meshes.verts_packed().shape[0]
|
||||
|
||||
# Features.
|
||||
x = torch.randn(
|
||||
total_verts, gconv_dim, device=device, requires_grad=True
|
||||
)
|
||||
x = torch.randn(total_verts, gconv_dim, device=device, requires_grad=True)
|
||||
torch.cuda.synchronize()
|
||||
|
||||
def run_graph_conv():
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
import unittest
|
||||
from itertools import product
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d.ops.knn import _knn_points_idx_naive, knn_points_idx
|
||||
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.renderer.lighting import DirectionalLights, PointLights
|
||||
from pytorch3d.transforms import RotateAxisAngle
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
|
||||
|
||||
class TestLights(TestCaseMixin, unittest.TestCase):
|
||||
def test_init_lights(self):
|
||||
@ -56,9 +55,7 @@ class TestLights(TestCaseMixin, unittest.TestCase):
|
||||
self.assertSeparate(new_prop, prop)
|
||||
|
||||
def test_lights_accessor(self):
|
||||
d_light = DirectionalLights(
|
||||
ambient_color=((0.0, 0.0, 0.0), (1.0, 1.0, 1.0))
|
||||
)
|
||||
d_light = DirectionalLights(ambient_color=((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)))
|
||||
p_light = PointLights(ambient_color=((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)))
|
||||
for light in [d_light, p_light]:
|
||||
# Update element
|
||||
@ -96,14 +93,12 @@ class TestLights(TestCaseMixin, unittest.TestCase):
|
||||
"""
|
||||
with self.assertRaises(ValueError):
|
||||
DirectionalLights(
|
||||
ambient_color=torch.randn(10, 3),
|
||||
diffuse_color=torch.randn(15, 3),
|
||||
ambient_color=torch.randn(10, 3), diffuse_color=torch.randn(15, 3)
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
PointLights(
|
||||
ambient_color=torch.randn(10, 3),
|
||||
diffuse_color=torch.randn(15, 3),
|
||||
ambient_color=torch.randn(10, 3), diffuse_color=torch.randn(15, 3)
|
||||
)
|
||||
|
||||
def test_initialize_lights_dimensions_fail(self):
|
||||
@ -138,8 +133,7 @@ class TestDiffuseLighting(TestCaseMixin, unittest.TestCase):
|
||||
normals = torch.tensor([0, 0, 1], dtype=torch.float32)
|
||||
normals = normals[None, None, :]
|
||||
expected_output = torch.tensor(
|
||||
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)],
|
||||
dtype=torch.float32,
|
||||
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32
|
||||
)
|
||||
expected_output = expected_output.view(1, 1, 3).repeat(3, 1, 1)
|
||||
light = DirectionalLights(diffuse_color=color, direction=direction)
|
||||
@ -169,13 +163,10 @@ class TestDiffuseLighting(TestCaseMixin, unittest.TestCase):
|
||||
points = torch.tensor([0, 0, 0], dtype=torch.float32)
|
||||
normals = torch.tensor([0, 0, 1], dtype=torch.float32)
|
||||
expected_output = torch.tensor(
|
||||
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)],
|
||||
dtype=torch.float32,
|
||||
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32
|
||||
)
|
||||
expected_output = expected_output.view(-1, 1, 3)
|
||||
light = PointLights(
|
||||
diffuse_color=color[None, :], location=location[None, :]
|
||||
)
|
||||
light = PointLights(diffuse_color=color[None, :], location=location[None, :])
|
||||
output_light = light.diffuse(
|
||||
points=points[None, None, :], normals=normals[None, None, :]
|
||||
)
|
||||
@ -184,9 +175,7 @@ class TestDiffuseLighting(TestCaseMixin, unittest.TestCase):
|
||||
# Change light direction to be 90 degrees apart from normal direction.
|
||||
location = torch.tensor([0, 1, 0], dtype=torch.float32)
|
||||
expected_output = torch.zeros_like(expected_output)
|
||||
light = PointLights(
|
||||
diffuse_color=color[None, :], location=location[None, :]
|
||||
)
|
||||
light = PointLights(diffuse_color=color[None, :], location=location[None, :])
|
||||
output_light = light.diffuse(
|
||||
points=points[None, None, :], normals=normals[None, None, :]
|
||||
)
|
||||
@ -204,8 +193,7 @@ class TestDiffuseLighting(TestCaseMixin, unittest.TestCase):
|
||||
)
|
||||
normals = torch.tensor([0, 0, 1], dtype=torch.float32)
|
||||
expected_out = torch.tensor(
|
||||
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)],
|
||||
dtype=torch.float32,
|
||||
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32
|
||||
)
|
||||
|
||||
# Reshape
|
||||
@ -231,8 +219,7 @@ class TestDiffuseLighting(TestCaseMixin, unittest.TestCase):
|
||||
)
|
||||
normals = torch.tensor([0, 0, 1], dtype=torch.float32)
|
||||
expected_out = torch.tensor(
|
||||
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)],
|
||||
dtype=torch.float32,
|
||||
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32
|
||||
)
|
||||
|
||||
# Reshape
|
||||
@ -258,9 +245,7 @@ class TestDiffuseLighting(TestCaseMixin, unittest.TestCase):
|
||||
device = torch.device("cuda:0")
|
||||
color = torch.tensor([1, 1, 1], dtype=torch.float32, device=device)
|
||||
direction = torch.tensor(
|
||||
[0, 1 / np.sqrt(2), 1 / np.sqrt(2)],
|
||||
dtype=torch.float32,
|
||||
device=device,
|
||||
[0, 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32, device=device
|
||||
)
|
||||
normals = torch.tensor([0, 0, 1], dtype=torch.float32, device=device)
|
||||
normals = normals.view(1, 1, 1, 1, 3).expand(N, H, W, K, -1)
|
||||
@ -373,9 +358,7 @@ class TestSpecularLighting(TestCaseMixin, unittest.TestCase):
|
||||
normals = torch.tensor([0, 1, 0], dtype=torch.float32)
|
||||
expected_output = torch.tensor([1.0, 0.0, 1.0], dtype=torch.float32)
|
||||
expected_output = expected_output.view(-1, 1, 3)
|
||||
lights = PointLights(
|
||||
specular_color=color[None, :], location=location[None, :]
|
||||
)
|
||||
lights = PointLights(specular_color=color[None, :], location=location[None, :])
|
||||
output_light = lights.specular(
|
||||
points=points[None, None, :],
|
||||
normals=normals[None, None, :],
|
||||
@ -528,8 +511,7 @@ class TestSpecularLighting(TestCaseMixin, unittest.TestCase):
|
||||
mesh_to_vert_idx = torch.tensor(mesh_to_vert_idx, dtype=torch.int64)
|
||||
color = torch.tensor([[1, 1, 1], [1, 0, 1]], dtype=torch.float32)
|
||||
direction = torch.tensor(
|
||||
[[-1 / np.sqrt(2), 1 / np.sqrt(2), 0], [-1, 1, 0]],
|
||||
dtype=torch.float32,
|
||||
[[-1 / np.sqrt(2), 1 / np.sqrt(2), 0], [-1, 1, 0]], dtype=torch.float32
|
||||
)
|
||||
camera_position = torch.tensor(
|
||||
[
|
||||
|
@ -1,11 +1,10 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
|
||||
from pytorch3d.renderer.materials import Materials
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.renderer.materials import Materials
|
||||
|
||||
|
||||
class TestMaterials(TestCaseMixin, unittest.TestCase):
|
||||
@ -64,8 +63,7 @@ class TestMaterials(TestCaseMixin, unittest.TestCase):
|
||||
"""
|
||||
with self.assertRaises(ValueError):
|
||||
Materials(
|
||||
ambient_color=torch.randn(10, 3),
|
||||
diffuse_color=torch.randn(15, 3),
|
||||
ambient_color=torch.randn(10, 3), diffuse_color=torch.randn(15, 3)
|
||||
)
|
||||
|
||||
def test_initialize_materials_dimensions_fail(self):
|
||||
@ -80,16 +78,12 @@ class TestMaterials(TestCaseMixin, unittest.TestCase):
|
||||
Materials(shininess=torch.randn(10, 2))
|
||||
|
||||
def test_initialize_materials_mixed_inputs(self):
|
||||
mat = Materials(
|
||||
ambient_color=torch.randn(1, 3), diffuse_color=((1, 1, 1),)
|
||||
)
|
||||
mat = Materials(ambient_color=torch.randn(1, 3), diffuse_color=((1, 1, 1),))
|
||||
self.assertTrue(mat.ambient_color.shape == (1, 3))
|
||||
self.assertTrue(mat.diffuse_color.shape == (1, 3))
|
||||
|
||||
def test_initialize_materials_mixed_inputs_broadcast(self):
|
||||
mat = Materials(
|
||||
ambient_color=torch.randn(10, 3), diffuse_color=((1, 1, 1),)
|
||||
)
|
||||
mat = Materials(ambient_color=torch.randn(10, 3), diffuse_color=((1, 1, 1),))
|
||||
self.assertTrue(mat.ambient_color.shape == (10, 3))
|
||||
self.assertTrue(mat.diffuse_color.shape == (10, 3))
|
||||
self.assertTrue(mat.specular_color.shape == (10, 3))
|
||||
|
@ -1,12 +1,11 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.loss import mesh_edge_loss
|
||||
from pytorch3d.structures import Meshes
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
from test_sample_points_from_meshes import TestSamplePoints
|
||||
|
||||
|
||||
@ -27,9 +26,7 @@ class TestMeshEdgeLoss(TestCaseMixin, unittest.TestCase):
|
||||
mesh = Meshes(verts=verts_list, faces=faces_list)
|
||||
loss = mesh_edge_loss(mesh, target_length=target_length)
|
||||
|
||||
self.assertClose(
|
||||
loss, torch.tensor([0.0], dtype=torch.float32, device=device)
|
||||
)
|
||||
self.assertClose(loss, torch.tensor([0.0], dtype=torch.float32, device=device))
|
||||
self.assertTrue(loss.requires_grad)
|
||||
|
||||
@staticmethod
|
||||
@ -53,9 +50,7 @@ class TestMeshEdgeLoss(TestCaseMixin, unittest.TestCase):
|
||||
num_edges = mesh_edges.size(0)
|
||||
for e in range(num_edges):
|
||||
v0, v1 = verts_edges[e, 0], verts_edges[e, 1]
|
||||
predlosses[b] += (
|
||||
(v0 - v1).norm(dim=0, p=2) - target_length
|
||||
) ** 2.0
|
||||
predlosses[b] += ((v0 - v1).norm(dim=0, p=2) - target_length) ** 2.0
|
||||
|
||||
if num_edges > 0:
|
||||
predlosses[b] = predlosses[b] / num_edges
|
||||
@ -96,12 +91,8 @@ class TestMeshEdgeLoss(TestCaseMixin, unittest.TestCase):
|
||||
self.assertClose(loss, predloss)
|
||||
|
||||
@staticmethod
|
||||
def mesh_edge_loss(
|
||||
num_meshes: int = 10, max_v: int = 100, max_f: int = 300
|
||||
):
|
||||
meshes = TestSamplePoints.init_meshes(
|
||||
num_meshes, max_v, max_f, device="cuda:0"
|
||||
)
|
||||
def mesh_edge_loss(num_meshes: int = 10, max_v: int = 100, max_f: int = 300):
|
||||
meshes = TestSamplePoints.init_meshes(num_meshes, max_v, max_f, device="cuda:0")
|
||||
torch.cuda.synchronize()
|
||||
|
||||
def compute_loss():
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d.loss.mesh_laplacian_smoothing import mesh_laplacian_smoothing
|
||||
from pytorch3d.structures.meshes import Meshes
|
||||
|
||||
@ -56,9 +56,7 @@ class TestLaplacianSmoothing(unittest.TestCase):
|
||||
V = verts_packed.shape[0]
|
||||
|
||||
L = torch.zeros((V, V), dtype=torch.float32, device=meshes.device)
|
||||
inv_areas = torch.zeros(
|
||||
(V, 1), dtype=torch.float32, device=meshes.device
|
||||
)
|
||||
inv_areas = torch.zeros((V, 1), dtype=torch.float32, device=meshes.device)
|
||||
|
||||
for f in faces_packed:
|
||||
v0 = verts_packed[f[0], :]
|
||||
@ -69,9 +67,7 @@ class TestLaplacianSmoothing(unittest.TestCase):
|
||||
C = (v0 - v1).norm()
|
||||
s = 0.5 * (A + B + C)
|
||||
|
||||
face_area = (
|
||||
(s * (s - A) * (s - B) * (s - C)).clamp_(min=1e-12).sqrt()
|
||||
)
|
||||
face_area = (s * (s - A) * (s - B) * (s - C)).clamp_(min=1e-12).sqrt()
|
||||
inv_areas[f[0]] += face_area
|
||||
inv_areas[f[1]] += face_area
|
||||
inv_areas[f[2]] += face_area
|
||||
@ -114,16 +110,13 @@ class TestLaplacianSmoothing(unittest.TestCase):
|
||||
return loss.sum() / len(meshes)
|
||||
|
||||
@staticmethod
|
||||
def init_meshes(
|
||||
num_meshes: int = 10, num_verts: int = 1000, num_faces: int = 3000
|
||||
):
|
||||
def init_meshes(num_meshes: int = 10, num_verts: int = 1000, num_faces: int = 3000):
|
||||
device = torch.device("cuda:0")
|
||||
verts_list = []
|
||||
faces_list = []
|
||||
for _ in range(num_meshes):
|
||||
verts = (
|
||||
torch.rand((num_verts, 3), dtype=torch.float32, device=device)
|
||||
* 2.0
|
||||
torch.rand((num_verts, 3), dtype=torch.float32, device=device) * 2.0
|
||||
- 1.0
|
||||
) # verts in the space of [-1, 1]
|
||||
faces = torch.stack(
|
||||
@ -148,9 +141,7 @@ class TestLaplacianSmoothing(unittest.TestCase):
|
||||
|
||||
# feats in list
|
||||
out = mesh_laplacian_smoothing(meshes, method="uniform")
|
||||
naive_out = TestLaplacianSmoothing.laplacian_smoothing_naive_uniform(
|
||||
meshes
|
||||
)
|
||||
naive_out = TestLaplacianSmoothing.laplacian_smoothing_naive_uniform(meshes)
|
||||
|
||||
self.assertTrue(torch.allclose(out, naive_out))
|
||||
|
||||
@ -190,9 +181,7 @@ class TestLaplacianSmoothing(unittest.TestCase):
|
||||
verts_list = []
|
||||
faces_list = []
|
||||
for _ in range(num_meshes):
|
||||
verts = torch.rand(
|
||||
(num_verts, 3), dtype=torch.float32, device=device
|
||||
)
|
||||
verts = torch.rand((num_verts, 3), dtype=torch.float32, device=device)
|
||||
faces = torch.randint(
|
||||
num_verts, size=(num_faces, 3), dtype=torch.int64, device=device
|
||||
)
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d.loss.mesh_normal_consistency import mesh_normal_consistency
|
||||
from pytorch3d.structures.meshes import Meshes
|
||||
from pytorch3d.utils.ico_sphere import ico_sphere
|
||||
@ -33,17 +33,14 @@ class TestMeshNormalConsistency(unittest.TestCase):
|
||||
return faces
|
||||
|
||||
@staticmethod
|
||||
def init_meshes(
|
||||
num_meshes: int = 10, num_verts: int = 1000, num_faces: int = 3000
|
||||
):
|
||||
def init_meshes(num_meshes: int = 10, num_verts: int = 1000, num_faces: int = 3000):
|
||||
device = torch.device("cuda:0")
|
||||
valid_faces = TestMeshNormalConsistency.init_faces(num_verts).to(device)
|
||||
verts_list = []
|
||||
faces_list = []
|
||||
for _ in range(num_meshes):
|
||||
verts = (
|
||||
torch.rand((num_verts, 3), dtype=torch.float32, device=device)
|
||||
* 2.0
|
||||
torch.rand((num_verts, 3), dtype=torch.float32, device=device) * 2.0
|
||||
- 1.0
|
||||
) # verts in the space of [-1, 1]
|
||||
"""
|
||||
@ -105,8 +102,7 @@ class TestMeshNormalConsistency(unittest.TestCase):
|
||||
(
|
||||
1
|
||||
- torch.cosine_similarity(
|
||||
normals[i].view(1, 3),
|
||||
-normals[j].view(1, 3),
|
||||
normals[i].view(1, 3), -normals[j].view(1, 3)
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -137,9 +133,7 @@ class TestMeshNormalConsistency(unittest.TestCase):
|
||||
device = torch.device("cuda:0")
|
||||
# mesh1 shown above
|
||||
verts1 = torch.rand((4, 3), dtype=torch.float32, device=device)
|
||||
faces1 = torch.tensor(
|
||||
[[0, 1, 2], [2, 1, 3]], dtype=torch.int64, device=device
|
||||
)
|
||||
faces1 = torch.tensor([[0, 1, 2], [2, 1, 3]], dtype=torch.int64, device=device)
|
||||
|
||||
# mesh2 is a cuboid with 8 verts, 12 faces and 18 edges
|
||||
verts2 = torch.tensor(
|
||||
@ -181,9 +175,7 @@ class TestMeshNormalConsistency(unittest.TestCase):
|
||||
[[0, 1, 2], [2, 1, 3], [2, 1, 4]], dtype=torch.int64, device=device
|
||||
)
|
||||
|
||||
meshes = Meshes(
|
||||
verts=[verts1, verts2, verts3], faces=[faces1, faces2, faces3]
|
||||
)
|
||||
meshes = Meshes(verts=[verts1, verts2, verts3], faces=[faces1, faces2, faces3])
|
||||
|
||||
# mesh1: normal consistency computation
|
||||
n0 = (verts1[1] - verts1[2]).cross(verts1[3] - verts1[2])
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d.renderer.mesh.utils import _clip_barycentric_coordinates
|
||||
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from pytorch3d.structures.meshes import Meshes
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.structures.meshes import Meshes
|
||||
|
||||
|
||||
class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
@ -54,9 +53,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
# For lists of faces and vertices, we can sample different v/f
|
||||
# per mesh.
|
||||
f = torch.randint(max_f, size=(num_meshes,), dtype=torch.int32)
|
||||
v = torch.randint(
|
||||
3, high=max_v, size=(num_meshes,), dtype=torch.int32
|
||||
)
|
||||
v = torch.randint(3, high=max_v, size=(num_meshes,), dtype=torch.int32)
|
||||
|
||||
# Generate the actual vertices and faces.
|
||||
for i in range(num_meshes):
|
||||
@ -90,12 +87,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
device=device,
|
||||
),
|
||||
torch.tensor(
|
||||
[
|
||||
[0.1, 0.3, 0.3],
|
||||
[0.6, 0.7, 0.8],
|
||||
[0.2, 0.3, 0.4],
|
||||
[0.1, 0.5, 0.3],
|
||||
],
|
||||
[[0.1, 0.3, 0.3], [0.6, 0.7, 0.8], [0.2, 0.3, 0.4], [0.1, 0.5, 0.3]],
|
||||
dtype=torch.float32,
|
||||
device=device,
|
||||
),
|
||||
@ -113,9 +105,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
]
|
||||
faces = [
|
||||
torch.tensor([[0, 1, 2]], dtype=torch.int64, device=device),
|
||||
torch.tensor(
|
||||
[[0, 1, 2], [1, 2, 3]], dtype=torch.int64, device=device
|
||||
),
|
||||
torch.tensor([[0, 1, 2], [1, 2, 3]], dtype=torch.int64, device=device),
|
||||
torch.tensor(
|
||||
[
|
||||
[1, 2, 0],
|
||||
@ -136,12 +126,8 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
mesh = TestMeshes.init_simple_mesh("cuda:0")
|
||||
|
||||
# Check that faces/verts per mesh are set in init:
|
||||
self.assertClose(
|
||||
mesh._num_faces_per_mesh.cpu(), torch.tensor([1, 2, 7])
|
||||
)
|
||||
self.assertClose(
|
||||
mesh._num_verts_per_mesh.cpu(), torch.tensor([3, 4, 5])
|
||||
)
|
||||
self.assertClose(mesh._num_faces_per_mesh.cpu(), torch.tensor([1, 2, 7]))
|
||||
self.assertClose(mesh._num_verts_per_mesh.cpu(), torch.tensor([3, 4, 5]))
|
||||
|
||||
# Check computed tensors
|
||||
self.assertClose(
|
||||
@ -163,8 +149,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
mesh.mesh_to_faces_packed_first_idx().cpu(), torch.tensor([0, 1, 3])
|
||||
)
|
||||
self.assertClose(
|
||||
mesh.num_edges_per_mesh().cpu(),
|
||||
torch.tensor([3, 5, 10], dtype=torch.int32),
|
||||
mesh.num_edges_per_mesh().cpu(), torch.tensor([3, 5, 10], dtype=torch.int32)
|
||||
)
|
||||
|
||||
def test_simple_random_meshes(self):
|
||||
@ -172,9 +157,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
# Define the test mesh object either as a list or tensor of faces/verts.
|
||||
for lists_to_tensors in (False, True):
|
||||
N = 10
|
||||
mesh = TestMeshes.init_mesh(
|
||||
N, 100, 300, lists_to_tensors=lists_to_tensors
|
||||
)
|
||||
mesh = TestMeshes.init_mesh(N, 100, 300, lists_to_tensors=lists_to_tensors)
|
||||
verts_list = mesh.verts_list()
|
||||
faces_list = mesh.faces_list()
|
||||
|
||||
@ -207,12 +190,8 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
for n in range(N):
|
||||
v = verts_list[n].shape[0]
|
||||
f = faces_list[n].shape[0]
|
||||
self.assertClose(
|
||||
verts_packed[curv : curv + v, :], verts_list[n]
|
||||
)
|
||||
self.assertClose(
|
||||
faces_packed[curf : curf + f, :] - curv, faces_list[n]
|
||||
)
|
||||
self.assertClose(verts_packed[curv : curv + v, :], verts_list[n])
|
||||
self.assertClose(faces_packed[curf : curf + f, :] - curv, faces_list[n])
|
||||
self.assertTrue(vert_to_mesh[curv : curv + v].eq(n).all())
|
||||
self.assertTrue(face_to_mesh[curf : curf + f].eq(n).all())
|
||||
self.assertTrue(mesh_to_vert[n] == curv)
|
||||
@ -232,9 +211,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
npedges = np.concatenate((e12, e20, e01), axis=0)
|
||||
npedges = np.sort(npedges, axis=1)
|
||||
|
||||
unique_edges, unique_idx = np.unique(
|
||||
npedges, return_index=True, axis=0
|
||||
)
|
||||
unique_edges, unique_idx = np.unique(npedges, return_index=True, axis=0)
|
||||
self.assertTrue(np.allclose(edges, unique_edges))
|
||||
temp = face_to_mesh.cpu().numpy()
|
||||
temp = np.concatenate((temp, temp, temp), axis=0)
|
||||
@ -266,13 +243,9 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
v = torch.randint(
|
||||
3, high=V, size=(1,), dtype=torch.int32, device=device
|
||||
)[0]
|
||||
f = torch.randint(
|
||||
F, size=(1,), dtype=torch.int32, device=device
|
||||
)[0]
|
||||
f = torch.randint(F, size=(1,), dtype=torch.int32, device=device)[0]
|
||||
verts = torch.rand((v, 3), dtype=torch.float32, device=device)
|
||||
faces = torch.randint(
|
||||
v, size=(f, 3), dtype=torch.int64, device=device
|
||||
)
|
||||
faces = torch.randint(v, size=(f, 3), dtype=torch.int64, device=device)
|
||||
else:
|
||||
verts = torch.tensor([], dtype=torch.float32, device=device)
|
||||
faces = torch.tensor([], dtype=torch.int64, device=device)
|
||||
@ -309,16 +282,12 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
)
|
||||
for n in range(N):
|
||||
verts.append(torch.rand((V, 3), dtype=torch.float32, device=device))
|
||||
this_faces = torch.full(
|
||||
(F, 3), -1, dtype=torch.int64, device=device
|
||||
)
|
||||
this_faces = torch.full((F, 3), -1, dtype=torch.int64, device=device)
|
||||
if valid[n]:
|
||||
v = torch.randint(
|
||||
3, high=V, size=(1,), dtype=torch.int32, device=device
|
||||
)[0]
|
||||
f = torch.randint(
|
||||
F, size=(1,), dtype=torch.int32, device=device
|
||||
)[0]
|
||||
f = torch.randint(F, size=(1,), dtype=torch.int32, device=device)[0]
|
||||
this_faces[:f, :] = torch.randint(
|
||||
v, size=(f, 3), dtype=torch.int64, device=device
|
||||
)
|
||||
@ -329,9 +298,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
mesh = Meshes(verts=torch.stack(verts), faces=torch.stack(faces))
|
||||
|
||||
# Check verts/faces per mesh are set correctly in init.
|
||||
self.assertListEqual(
|
||||
mesh._num_faces_per_mesh.tolist(), num_faces.tolist()
|
||||
)
|
||||
self.assertListEqual(mesh._num_faces_per_mesh.tolist(), num_faces.tolist())
|
||||
self.assertListEqual(mesh._num_verts_per_mesh.tolist(), [V] * N)
|
||||
|
||||
for n, (vv, ff) in enumerate(zip(mesh.verts_list(), mesh.faces_list())):
|
||||
@ -339,12 +306,8 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
self.assertClose(vv, verts[n])
|
||||
|
||||
new_faces = [ff.clone() for ff in faces]
|
||||
v = torch.randint(
|
||||
3, high=V, size=(1,), dtype=torch.int32, device=device
|
||||
)[0]
|
||||
f = torch.randint(F - 10, size=(1,), dtype=torch.int32, device=device)[
|
||||
0
|
||||
]
|
||||
v = torch.randint(3, high=V, size=(1,), dtype=torch.int32, device=device)[0]
|
||||
f = torch.randint(F - 10, size=(1,), dtype=torch.int32, device=device)[0]
|
||||
this_faces = torch.full((F, 3), -1, dtype=torch.int64, device=device)
|
||||
this_faces[10 : f + 10, :] = torch.randint(
|
||||
v, size=(f, 3), dtype=torch.int64, device=device
|
||||
@ -376,9 +339,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
torch.allclose(new_mesh._verts_list[0], mesh._verts_list[0])
|
||||
)
|
||||
self.assertFalse(
|
||||
torch.allclose(
|
||||
mesh.num_verts_per_mesh(), new_mesh.num_verts_per_mesh()
|
||||
)
|
||||
torch.allclose(mesh.num_verts_per_mesh(), new_mesh.num_verts_per_mesh())
|
||||
)
|
||||
self.assertSeparate(new_mesh.verts_packed(), mesh.verts_packed())
|
||||
self.assertSeparate(new_mesh.verts_padded(), mesh.verts_padded())
|
||||
@ -438,9 +399,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
mesh._compute_face_areas_normals(refresh=True)
|
||||
mesh._compute_vertex_normals(refresh=True)
|
||||
|
||||
deform = torch.rand(
|
||||
(all_v, 3), dtype=torch.float32, device=mesh.device
|
||||
)
|
||||
deform = torch.rand((all_v, 3), dtype=torch.float32, device=mesh.device)
|
||||
# new meshes class to hold the deformed mesh
|
||||
new_mesh_naive = naive_offset_verts(mesh, deform)
|
||||
|
||||
@ -458,9 +417,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
self.assertClose(
|
||||
new_mesh.verts_list()[i], new_mesh_naive.verts_list()[i]
|
||||
)
|
||||
self.assertClose(
|
||||
mesh.faces_list()[i], new_mesh_naive.faces_list()[i]
|
||||
)
|
||||
self.assertClose(mesh.faces_list()[i], new_mesh_naive.faces_list()[i])
|
||||
self.assertClose(
|
||||
new_mesh.faces_list()[i], new_mesh_naive.faces_list()[i]
|
||||
)
|
||||
@ -475,21 +432,11 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
)
|
||||
|
||||
# check padded & packed
|
||||
self.assertClose(
|
||||
new_mesh.faces_padded(), new_mesh_naive.faces_padded()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.verts_padded(), new_mesh_naive.verts_padded()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.faces_packed(), new_mesh_naive.faces_packed()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.verts_packed(), new_mesh_naive.verts_packed()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.edges_packed(), new_mesh_naive.edges_packed()
|
||||
)
|
||||
self.assertClose(new_mesh.faces_padded(), new_mesh_naive.faces_padded())
|
||||
self.assertClose(new_mesh.verts_padded(), new_mesh_naive.verts_padded())
|
||||
self.assertClose(new_mesh.faces_packed(), new_mesh_naive.faces_packed())
|
||||
self.assertClose(new_mesh.verts_packed(), new_mesh_naive.verts_packed())
|
||||
self.assertClose(new_mesh.edges_packed(), new_mesh_naive.edges_packed())
|
||||
self.assertClose(
|
||||
new_mesh.verts_packed_to_mesh_idx(),
|
||||
new_mesh_naive.verts_packed_to_mesh_idx(),
|
||||
@ -499,8 +446,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
new_mesh_naive.mesh_to_verts_packed_first_idx(),
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.num_verts_per_mesh(),
|
||||
new_mesh_naive.num_verts_per_mesh(),
|
||||
new_mesh.num_verts_per_mesh(), new_mesh_naive.num_verts_per_mesh()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.faces_packed_to_mesh_idx(),
|
||||
@ -511,8 +457,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
new_mesh_naive.mesh_to_faces_packed_first_idx(),
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.num_faces_per_mesh(),
|
||||
new_mesh_naive.num_faces_per_mesh(),
|
||||
new_mesh.num_faces_per_mesh(), new_mesh_naive.num_faces_per_mesh()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.edges_packed_to_mesh_idx(),
|
||||
@ -527,24 +472,19 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
# check face areas, normals and vertex normals
|
||||
self.assertClose(
|
||||
new_mesh.verts_normals_packed(),
|
||||
new_mesh_naive.verts_normals_packed(),
|
||||
new_mesh.verts_normals_packed(), new_mesh_naive.verts_normals_packed()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.verts_normals_padded(),
|
||||
new_mesh_naive.verts_normals_padded(),
|
||||
new_mesh.verts_normals_padded(), new_mesh_naive.verts_normals_padded()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.faces_normals_packed(),
|
||||
new_mesh_naive.faces_normals_packed(),
|
||||
new_mesh.faces_normals_packed(), new_mesh_naive.faces_normals_packed()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.faces_normals_padded(),
|
||||
new_mesh_naive.faces_normals_padded(),
|
||||
new_mesh.faces_normals_padded(), new_mesh_naive.faces_normals_padded()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.faces_areas_packed(),
|
||||
new_mesh_naive.faces_areas_packed(),
|
||||
new_mesh.faces_areas_packed(), new_mesh_naive.faces_areas_packed()
|
||||
)
|
||||
|
||||
def test_scale_verts(self):
|
||||
@ -579,13 +519,11 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
for i in range(N):
|
||||
if test == "tensor":
|
||||
self.assertClose(
|
||||
scales[i] * mesh.verts_list()[i],
|
||||
new_mesh.verts_list()[i],
|
||||
scales[i] * mesh.verts_list()[i], new_mesh.verts_list()[i]
|
||||
)
|
||||
else:
|
||||
self.assertClose(
|
||||
scales * mesh.verts_list()[i],
|
||||
new_mesh.verts_list()[i],
|
||||
scales * mesh.verts_list()[i], new_mesh.verts_list()[i]
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.verts_list()[i], new_mesh_naive.verts_list()[i]
|
||||
@ -607,21 +545,11 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
)
|
||||
|
||||
# check padded & packed
|
||||
self.assertClose(
|
||||
new_mesh.faces_padded(), new_mesh_naive.faces_padded()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.verts_padded(), new_mesh_naive.verts_padded()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.faces_packed(), new_mesh_naive.faces_packed()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.verts_packed(), new_mesh_naive.verts_packed()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.edges_packed(), new_mesh_naive.edges_packed()
|
||||
)
|
||||
self.assertClose(new_mesh.faces_padded(), new_mesh_naive.faces_padded())
|
||||
self.assertClose(new_mesh.verts_padded(), new_mesh_naive.verts_padded())
|
||||
self.assertClose(new_mesh.faces_packed(), new_mesh_naive.faces_packed())
|
||||
self.assertClose(new_mesh.verts_packed(), new_mesh_naive.verts_packed())
|
||||
self.assertClose(new_mesh.edges_packed(), new_mesh_naive.edges_packed())
|
||||
self.assertClose(
|
||||
new_mesh.verts_packed_to_mesh_idx(),
|
||||
new_mesh_naive.verts_packed_to_mesh_idx(),
|
||||
@ -631,8 +559,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
new_mesh_naive.mesh_to_verts_packed_first_idx(),
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.num_verts_per_mesh(),
|
||||
new_mesh_naive.num_verts_per_mesh(),
|
||||
new_mesh.num_verts_per_mesh(), new_mesh_naive.num_verts_per_mesh()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.faces_packed_to_mesh_idx(),
|
||||
@ -643,8 +570,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
new_mesh_naive.mesh_to_faces_packed_first_idx(),
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.num_faces_per_mesh(),
|
||||
new_mesh_naive.num_faces_per_mesh(),
|
||||
new_mesh.num_faces_per_mesh(), new_mesh_naive.num_faces_per_mesh()
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.edges_packed_to_mesh_idx(),
|
||||
@ -675,8 +601,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
new_mesh_naive.faces_normals_padded(),
|
||||
)
|
||||
self.assertClose(
|
||||
new_mesh.faces_areas_packed(),
|
||||
new_mesh_naive.faces_areas_packed(),
|
||||
new_mesh.faces_areas_packed(), new_mesh_naive.faces_areas_packed()
|
||||
)
|
||||
|
||||
def test_extend_list(self):
|
||||
@ -730,10 +655,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
self.assertTrue(len(split_meshes[0]) == 2)
|
||||
self.assertTrue(
|
||||
split_meshes[0].verts_list()
|
||||
== [
|
||||
mesh.get_mesh_verts_faces(0)[0],
|
||||
mesh.get_mesh_verts_faces(1)[0],
|
||||
]
|
||||
== [mesh.get_mesh_verts_faces(0)[0], mesh.get_mesh_verts_faces(1)[0]]
|
||||
)
|
||||
self.assertTrue(len(split_meshes[1]) == 3)
|
||||
self.assertTrue(
|
||||
@ -756,9 +678,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
verts_faces = [(10, 100), (20, 200)]
|
||||
for (V, F) in verts_faces:
|
||||
verts = torch.rand((V, 3), dtype=torch.float32, device=device)
|
||||
faces = torch.randint(
|
||||
V, size=(F, 3), dtype=torch.int64, device=device
|
||||
)
|
||||
faces = torch.randint(V, size=(F, 3), dtype=torch.int64, device=device)
|
||||
verts_list.append(verts)
|
||||
faces_list.append(faces)
|
||||
|
||||
@ -782,9 +702,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
faces_list = []
|
||||
for (V, F) in [(10, 100)]:
|
||||
verts = torch.rand((V, 3), dtype=torch.float32, device=device)
|
||||
faces = torch.randint(
|
||||
V, size=(F, 3), dtype=torch.int64, device=device
|
||||
)
|
||||
faces = torch.randint(V, size=(F, 3), dtype=torch.int64, device=device)
|
||||
verts_list.append(verts)
|
||||
faces_list.append(faces)
|
||||
|
||||
@ -802,9 +720,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
verts_faces = [(10, 100), (20, 200), (30, 300)]
|
||||
for (V, F) in verts_faces:
|
||||
verts = torch.rand((V, 3), dtype=torch.float32, device=device)
|
||||
faces = torch.randint(
|
||||
V, size=(F, 3), dtype=torch.int64, device=device
|
||||
)
|
||||
faces = torch.randint(V, size=(F, 3), dtype=torch.int64, device=device)
|
||||
verts_list.append(verts)
|
||||
faces_list.append(faces)
|
||||
|
||||
@ -814,9 +730,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
verts_padded = mesh.verts_padded()
|
||||
verts_padded_flat = verts_padded.view(-1, 3)
|
||||
|
||||
self.assertClose(
|
||||
verts_padded_flat[verts_padded_to_packed_idx], verts_packed
|
||||
)
|
||||
self.assertClose(verts_padded_flat[verts_padded_to_packed_idx], verts_packed)
|
||||
|
||||
idx = verts_padded_to_packed_idx.view(-1, 1).expand(-1, 3)
|
||||
self.assertClose(verts_padded_flat.gather(0, idx), verts_packed)
|
||||
@ -828,9 +742,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
verts_faces = [(10, 100), (20, 200), (30, 300)]
|
||||
for (V, F) in verts_faces:
|
||||
verts = torch.rand((V, 3), dtype=torch.float32, device=device)
|
||||
faces = torch.randint(
|
||||
V, size=(F, 3), dtype=torch.int64, device=device
|
||||
)
|
||||
faces = torch.randint(V, size=(F, 3), dtype=torch.int64, device=device)
|
||||
verts_list.append(verts)
|
||||
faces_list.append(faces)
|
||||
|
||||
@ -1006,12 +918,10 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
verts_normals_packed = meshes.verts_normals_packed()
|
||||
faces_normals_packed = meshes.faces_normals_packed()
|
||||
self.assertTrue(
|
||||
list(verts_normals_packed.shape)
|
||||
== [verts.shape[0] + verts2.shape[0], 3]
|
||||
list(verts_normals_packed.shape) == [verts.shape[0] + verts2.shape[0], 3]
|
||||
)
|
||||
self.assertTrue(
|
||||
list(faces_normals_packed.shape)
|
||||
== [faces.shape[0] + faces2.shape[0], 3]
|
||||
list(faces_normals_packed.shape) == [faces.shape[0] + faces2.shape[0], 3]
|
||||
)
|
||||
|
||||
# Single mesh where two faces share one vertex so the normal is
|
||||
@ -1079,17 +989,12 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
# with areas > eps=1e-6
|
||||
nonzero = face_areas_cpu > 1e-6
|
||||
self.assertClose(
|
||||
face_normals_cpu[nonzero],
|
||||
face_normals_cuda.cpu()[nonzero],
|
||||
atol=1e-6,
|
||||
face_normals_cpu[nonzero], face_normals_cuda.cpu()[nonzero], atol=1e-6
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def compute_packed_with_init(
|
||||
num_meshes: int = 10,
|
||||
max_v: int = 100,
|
||||
max_f: int = 300,
|
||||
device: str = "cpu",
|
||||
num_meshes: int = 10, max_v: int = 100, max_f: int = 300, device: str = "cpu"
|
||||
):
|
||||
mesh = TestMeshes.init_mesh(num_meshes, max_v, max_f, device=device)
|
||||
torch.cuda.synchronize()
|
||||
@ -1102,10 +1007,7 @@ class TestMeshes(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
@staticmethod
|
||||
def compute_padded_with_init(
|
||||
num_meshes: int = 10,
|
||||
max_v: int = 100,
|
||||
max_f: int = 300,
|
||||
device: str = "cpu",
|
||||
num_meshes: int = 10, max_v: int = 100, max_f: int = 300, device: str = "cpu"
|
||||
):
|
||||
mesh = TestMeshes.init_mesh(num_meshes, max_v, max_f, device=device)
|
||||
torch.cuda.synchronize()
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
import unittest
|
||||
from itertools import product
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from pytorch3d import _C
|
||||
|
||||
|
||||
@ -33,9 +33,7 @@ class TestNearestNeighborPoints(unittest.TestCase):
|
||||
# to the cpp or cuda versions of the function
|
||||
# depending on the input type.
|
||||
idx1 = _C.nn_points_idx(x, y)
|
||||
idx2 = TestNearestNeighborPoints.nn_points_idx_naive(
|
||||
x, y
|
||||
)
|
||||
idx2 = TestNearestNeighborPoints.nn_points_idx_naive(x, y)
|
||||
self.assertTrue(idx1.size(1) == P1)
|
||||
self.assertTrue(torch.all(idx1 == idx2))
|
||||
|
||||
|
@ -4,14 +4,13 @@ import os
|
||||
import unittest
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.io import load_obj, load_objs_as_meshes, save_obj
|
||||
from pytorch3d.structures import Meshes, Textures, join_meshes
|
||||
from pytorch3d.utils import torus
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
|
||||
|
||||
class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
def test_load_obj_simple(self):
|
||||
@ -34,12 +33,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
tex_maps = aux.texture_images
|
||||
|
||||
expected_verts = torch.tensor(
|
||||
[
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.2, 0.3, 0.4],
|
||||
[0.3, 0.4, 0.5],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
[[0.1, 0.2, 0.3], [0.2, 0.3, 0.4], [0.3, 0.4, 0.5], [0.4, 0.5, 0.6]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
expected_faces = torch.tensor(
|
||||
@ -124,12 +118,8 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
[[0.749279, 0.501284], [0.999110, 0.501077], [0.999455, 0.750380]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
expected_faces_normals_idx = torch.tensor(
|
||||
[[1, 1, 1]], dtype=torch.int64
|
||||
)
|
||||
expected_faces_textures_idx = torch.tensor(
|
||||
[[0, 0, 1]], dtype=torch.int64
|
||||
)
|
||||
expected_faces_normals_idx = torch.tensor([[1, 1, 1]], dtype=torch.int64)
|
||||
expected_faces_textures_idx = torch.tensor([[0, 0, 1]], dtype=torch.int64)
|
||||
|
||||
self.assertTrue(torch.all(verts == expected_verts))
|
||||
self.assertTrue(torch.all(faces.verts_idx == expected_faces))
|
||||
@ -153,23 +143,13 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
]
|
||||
)
|
||||
obj_file = StringIO(obj_file)
|
||||
expected_faces_normals_idx = torch.tensor(
|
||||
[[0, 0, 1]], dtype=torch.int64
|
||||
)
|
||||
expected_faces_normals_idx = torch.tensor([[0, 0, 1]], dtype=torch.int64)
|
||||
expected_normals = torch.tensor(
|
||||
[
|
||||
[0.000000, 0.000000, -1.000000],
|
||||
[-1.000000, -0.000000, -0.000000],
|
||||
],
|
||||
[[0.000000, 0.000000, -1.000000], [-1.000000, -0.000000, -0.000000]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
expected_verts = torch.tensor(
|
||||
[
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.2, 0.3, 0.4],
|
||||
[0.3, 0.4, 0.5],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
[[0.1, 0.2, 0.3], [0.2, 0.3, 0.4], [0.3, 0.4, 0.5], [0.4, 0.5, 0.6]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
verts, faces, aux = load_obj(obj_file)
|
||||
@ -198,19 +178,12 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
]
|
||||
)
|
||||
obj_file = StringIO(obj_file)
|
||||
expected_faces_textures_idx = torch.tensor(
|
||||
[[0, 0, 1]], dtype=torch.int64
|
||||
)
|
||||
expected_faces_textures_idx = torch.tensor([[0, 0, 1]], dtype=torch.int64)
|
||||
expected_textures = torch.tensor(
|
||||
[[0.999110, 0.501077], [0.999455, 0.750380]], dtype=torch.float32
|
||||
)
|
||||
expected_verts = torch.tensor(
|
||||
[
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.2, 0.3, 0.4],
|
||||
[0.3, 0.4, 0.5],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
[[0.1, 0.2, 0.3], [0.2, 0.3, 0.4], [0.3, 0.4, 0.5], [0.4, 0.5, 0.6]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
verts, faces, aux = load_obj(obj_file)
|
||||
@ -257,9 +230,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
load_obj(obj_file)
|
||||
self.assertTrue(
|
||||
"Vertex properties are inconsistent" in str(err.exception)
|
||||
)
|
||||
self.assertTrue("Vertex properties are inconsistent" in str(err.exception))
|
||||
|
||||
def test_load_obj_error_too_many_vertex_properties(self):
|
||||
obj_file = "\n".join(["f 2/1/1/3"])
|
||||
@ -267,9 +238,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
with self.assertRaises(ValueError) as err:
|
||||
load_obj(obj_file)
|
||||
self.assertTrue(
|
||||
"Face vertices can ony have 3 properties" in str(err.exception)
|
||||
)
|
||||
self.assertTrue("Face vertices can ony have 3 properties" in str(err.exception))
|
||||
|
||||
def test_load_obj_error_invalid_vertex_indices(self):
|
||||
obj_file = "\n".join(
|
||||
@ -320,7 +289,9 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
verts = torch.FloatTensor([[0.1, 0.2, 0.3, 0.4]]) # (V, 4)
|
||||
faces = torch.LongTensor([[0, 1, 2]])
|
||||
save_obj(StringIO(), verts, faces)
|
||||
expected_message = "Argument 'verts' should either be empty or of shape (num_verts, 3)."
|
||||
expected_message = (
|
||||
"Argument 'verts' should either be empty or of shape (num_verts, 3)."
|
||||
)
|
||||
self.assertTrue(expected_message, error.exception)
|
||||
|
||||
# Invalid faces shape
|
||||
@ -328,7 +299,9 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
verts = torch.FloatTensor([[0.1, 0.2, 0.3]])
|
||||
faces = torch.LongTensor([[0, 1, 2, 3]]) # (F, 4)
|
||||
save_obj(StringIO(), verts, faces)
|
||||
expected_message = "Argument 'faces' should either be empty or of shape (num_faces, 3)."
|
||||
expected_message = (
|
||||
"Argument 'faces' should either be empty or of shape (num_faces, 3)."
|
||||
)
|
||||
self.assertTrue(expected_message, error.exception)
|
||||
|
||||
def test_save_obj_invalid_indices(self):
|
||||
@ -395,12 +368,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
def test_save_obj(self):
|
||||
verts = torch.tensor(
|
||||
[
|
||||
[0.01, 0.2, 0.301],
|
||||
[0.2, 0.03, 0.408],
|
||||
[0.3, 0.4, 0.05],
|
||||
[0.6, 0.7, 0.8],
|
||||
],
|
||||
[[0.01, 0.2, 0.301], [0.2, 0.03, 0.408], [0.3, 0.4, 0.05], [0.6, 0.7, 0.8]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
faces = torch.tensor(
|
||||
@ -424,9 +392,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
self.assertEqual(actual_file, expected_file)
|
||||
|
||||
def test_load_mtl(self):
|
||||
DATA_DIR = (
|
||||
Path(__file__).resolve().parent.parent / "docs/tutorials/data"
|
||||
)
|
||||
DATA_DIR = Path(__file__).resolve().parent.parent / "docs/tutorials/data"
|
||||
obj_filename = "cow_mesh/cow.obj"
|
||||
filename = os.path.join(DATA_DIR, obj_filename)
|
||||
verts, faces, aux = load_obj(filename)
|
||||
@ -452,19 +418,13 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
# Check all keys and values in dictionary are the same.
|
||||
for n1, n2 in zip(materials.keys(), expected_materials.keys()):
|
||||
self.assertTrue(n1 == n2)
|
||||
for k1, k2 in zip(
|
||||
materials[n1].keys(), expected_materials[n2].keys()
|
||||
):
|
||||
for k1, k2 in zip(materials[n1].keys(), expected_materials[n2].keys()):
|
||||
self.assertTrue(
|
||||
torch.allclose(
|
||||
materials[n1][k1], expected_materials[n2][k2]
|
||||
)
|
||||
torch.allclose(materials[n1][k1], expected_materials[n2][k2])
|
||||
)
|
||||
|
||||
def test_load_mtl_noload(self):
|
||||
DATA_DIR = (
|
||||
Path(__file__).resolve().parent.parent / "docs/tutorials/data"
|
||||
)
|
||||
DATA_DIR = Path(__file__).resolve().parent.parent / "docs/tutorials/data"
|
||||
obj_filename = "cow_mesh/cow.obj"
|
||||
filename = os.path.join(DATA_DIR, obj_filename)
|
||||
verts, faces, aux = load_obj(filename, load_textures=False)
|
||||
@ -490,12 +450,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
verts, faces, aux = load_obj(obj_file)
|
||||
|
||||
expected_verts = torch.tensor(
|
||||
[
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.2, 0.3, 0.4],
|
||||
[0.3, 0.4, 0.5],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
[[0.1, 0.2, 0.3], [0.2, 0.3, 0.4], [0.3, 0.4, 0.5], [0.4, 0.5, 0.6]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
expected_faces = torch.tensor([[0, 1, 2], [0, 1, 3]], dtype=torch.int64)
|
||||
@ -514,12 +469,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
verts, faces, aux = load_obj(filename)
|
||||
|
||||
expected_verts = torch.tensor(
|
||||
[
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.2, 0.3, 0.4],
|
||||
[0.3, 0.4, 0.5],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
[[0.1, 0.2, 0.3], [0.2, 0.3, 0.4], [0.3, 0.4, 0.5], [0.4, 0.5, 0.6]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
expected_faces = torch.tensor([[0, 1, 2], [0, 1, 3]], dtype=torch.int64)
|
||||
@ -533,12 +483,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
verts, faces, aux = load_obj(filename, load_textures=False)
|
||||
|
||||
expected_verts = torch.tensor(
|
||||
[
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.2, 0.3, 0.4],
|
||||
[0.3, 0.4, 0.5],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
[[0.1, 0.2, 0.3], [0.2, 0.3, 0.4], [0.3, 0.4, 0.5], [0.4, 0.5, 0.6]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
expected_faces = torch.tensor([[0, 1, 2], [0, 1, 3]], dtype=torch.int64)
|
||||
@ -555,12 +500,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
verts, faces, aux = load_obj(filename)
|
||||
|
||||
expected_verts = torch.tensor(
|
||||
[
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.2, 0.3, 0.4],
|
||||
[0.3, 0.4, 0.5],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
[[0.1, 0.2, 0.3], [0.2, 0.3, 0.4], [0.3, 0.4, 0.5], [0.4, 0.5, 0.6]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
expected_faces = torch.tensor([[0, 1, 2], [0, 1, 3]], dtype=torch.int64)
|
||||
@ -574,12 +514,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
verts, faces, aux = load_obj(filename, load_textures=False)
|
||||
|
||||
expected_verts = torch.tensor(
|
||||
[
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.2, 0.3, 0.4],
|
||||
[0.3, 0.4, 0.5],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
[[0.1, 0.2, 0.3], [0.2, 0.3, 0.4], [0.3, 0.4, 0.5], [0.4, 0.5, 0.6]],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
expected_faces = torch.tensor([[0, 1, 2], [0, 1, 3]], dtype=torch.int64)
|
||||
@ -607,33 +542,24 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
check_item(mesh.verts_padded(), mesh3.verts_padded())
|
||||
check_item(mesh.faces_padded(), mesh3.faces_padded())
|
||||
if mesh.textures is not None:
|
||||
check_item(mesh.textures.maps_padded(), mesh3.textures.maps_padded())
|
||||
check_item(
|
||||
mesh.textures.maps_padded(), mesh3.textures.maps_padded()
|
||||
mesh.textures.faces_uvs_padded(), mesh3.textures.faces_uvs_padded()
|
||||
)
|
||||
check_item(
|
||||
mesh.textures.faces_uvs_padded(),
|
||||
mesh3.textures.faces_uvs_padded(),
|
||||
mesh.textures.verts_uvs_padded(), mesh3.textures.verts_uvs_padded()
|
||||
)
|
||||
check_item(
|
||||
mesh.textures.verts_uvs_padded(),
|
||||
mesh3.textures.verts_uvs_padded(),
|
||||
)
|
||||
check_item(
|
||||
mesh.textures.verts_rgb_padded(),
|
||||
mesh3.textures.verts_rgb_padded(),
|
||||
mesh.textures.verts_rgb_padded(), mesh3.textures.verts_rgb_padded()
|
||||
)
|
||||
|
||||
DATA_DIR = (
|
||||
Path(__file__).resolve().parent.parent / "docs/tutorials/data"
|
||||
)
|
||||
DATA_DIR = Path(__file__).resolve().parent.parent / "docs/tutorials/data"
|
||||
obj_filename = DATA_DIR / "cow_mesh/cow.obj"
|
||||
|
||||
mesh = load_objs_as_meshes([obj_filename])
|
||||
mesh3 = load_objs_as_meshes([obj_filename, obj_filename, obj_filename])
|
||||
check_triple(mesh, mesh3)
|
||||
self.assertTupleEqual(
|
||||
mesh.textures.maps_padded().shape, (1, 1024, 1024, 3)
|
||||
)
|
||||
self.assertTupleEqual(mesh.textures.maps_padded().shape, (1, 1024, 1024, 3))
|
||||
|
||||
mesh_notex = load_objs_as_meshes([obj_filename], load_textures=False)
|
||||
mesh3_notex = load_objs_as_meshes(
|
||||
@ -655,9 +581,7 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
teapot_obj = DATA_DIR / "teapot.obj"
|
||||
mesh_teapot = load_objs_as_meshes([teapot_obj])
|
||||
teapot_verts, teapot_faces = mesh_teapot.get_mesh_verts_faces(0)
|
||||
mix_mesh = load_objs_as_meshes(
|
||||
[obj_filename, teapot_obj], load_textures=False
|
||||
)
|
||||
mix_mesh = load_objs_as_meshes([obj_filename, teapot_obj], load_textures=False)
|
||||
self.assertEqual(len(mix_mesh), 2)
|
||||
self.assertClose(mix_mesh.verts_list()[0], mesh.verts_list()[0])
|
||||
self.assertClose(mix_mesh.faces_list()[0], mesh.faces_list()[0])
|
||||
@ -671,15 +595,11 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
self.assertClose(cow3_tea.faces_list()[3], mesh_teapot.faces_list()[0])
|
||||
|
||||
@staticmethod
|
||||
def _bm_save_obj(
|
||||
verts: torch.Tensor, faces: torch.Tensor, decimal_places: int
|
||||
):
|
||||
def _bm_save_obj(verts: torch.Tensor, faces: torch.Tensor, decimal_places: int):
|
||||
return lambda: save_obj(StringIO(), verts, faces, decimal_places)
|
||||
|
||||
@staticmethod
|
||||
def _bm_load_obj(
|
||||
verts: torch.Tensor, faces: torch.Tensor, decimal_places: int
|
||||
):
|
||||
def _bm_load_obj(verts: torch.Tensor, faces: torch.Tensor, decimal_places: int):
|
||||
f = StringIO()
|
||||
save_obj(f, verts, faces, decimal_places)
|
||||
s = f.getvalue()
|
||||
|
@ -1,13 +1,12 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.ops import packed_to_padded, padded_to_packed
|
||||
from pytorch3d.structures.meshes import Meshes
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
|
||||
|
||||
class TestPackedToPadded(TestCaseMixin, unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
@ -25,9 +24,7 @@ class TestPackedToPadded(TestCaseMixin, unittest.TestCase):
|
||||
verts_list = []
|
||||
faces_list = []
|
||||
for _ in range(num_meshes):
|
||||
verts = torch.rand(
|
||||
(num_verts, 3), dtype=torch.float32, device=device
|
||||
)
|
||||
verts = torch.rand((num_verts, 3), dtype=torch.float32, device=device)
|
||||
faces = torch.randint(
|
||||
num_verts, size=(num_faces, 3), dtype=torch.int64, device=device
|
||||
)
|
||||
@ -47,9 +44,7 @@ class TestPackedToPadded(TestCaseMixin, unittest.TestCase):
|
||||
if D == 0:
|
||||
inputs_padded = torch.zeros((num_meshes, max_size), device=device)
|
||||
else:
|
||||
inputs_padded = torch.zeros(
|
||||
(num_meshes, max_size, D), device=device
|
||||
)
|
||||
inputs_padded = torch.zeros((num_meshes, max_size, D), device=device)
|
||||
for m in range(num_meshes):
|
||||
s = first_idxs[m]
|
||||
if m == num_meshes - 1:
|
||||
@ -92,13 +87,9 @@ class TestPackedToPadded(TestCaseMixin, unittest.TestCase):
|
||||
max_faces = meshes.num_faces_per_mesh().max().item()
|
||||
|
||||
if D == 0:
|
||||
values = torch.rand(
|
||||
(faces.shape[0],), device=device, requires_grad=True
|
||||
)
|
||||
values = torch.rand((faces.shape[0],), device=device, requires_grad=True)
|
||||
else:
|
||||
values = torch.rand(
|
||||
(faces.shape[0], D), device=device, requires_grad=True
|
||||
)
|
||||
values = torch.rand((faces.shape[0], D), device=device, requires_grad=True)
|
||||
values_torch = values.detach().clone()
|
||||
values_torch.requires_grad = True
|
||||
values_padded = packed_to_padded(
|
||||
@ -120,10 +111,7 @@ class TestPackedToPadded(TestCaseMixin, unittest.TestCase):
|
||||
values_padded_torch.backward(grad_inputs)
|
||||
grad_outputs_torch1 = values_torch.grad
|
||||
grad_outputs_torch2 = TestPackedToPadded.padded_to_packed_python(
|
||||
grad_inputs,
|
||||
mesh_to_faces_packed_first_idx,
|
||||
values.size(0),
|
||||
device=device,
|
||||
grad_inputs, mesh_to_faces_packed_first_idx, values.size(0), device=device
|
||||
)
|
||||
self.assertClose(grad_outputs, grad_outputs_torch1)
|
||||
self.assertClose(grad_outputs, grad_outputs_torch2)
|
||||
@ -165,9 +153,7 @@ class TestPackedToPadded(TestCaseMixin, unittest.TestCase):
|
||||
values_torch = values.detach().clone()
|
||||
values_torch.requires_grad = True
|
||||
values_packed = padded_to_packed(
|
||||
values,
|
||||
mesh_to_faces_packed_first_idx,
|
||||
num_faces_per_mesh.sum().item(),
|
||||
values, mesh_to_faces_packed_first_idx, num_faces_per_mesh.sum().item()
|
||||
)
|
||||
values_packed_torch = TestPackedToPadded.padded_to_packed_python(
|
||||
values_torch,
|
||||
@ -180,9 +166,7 @@ class TestPackedToPadded(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
# check backward
|
||||
if D == 0:
|
||||
grad_inputs = torch.rand(
|
||||
(num_faces_per_mesh.sum().item()), device=device
|
||||
)
|
||||
grad_inputs = torch.rand((num_faces_per_mesh.sum().item()), device=device)
|
||||
else:
|
||||
grad_inputs = torch.rand(
|
||||
(num_faces_per_mesh.sum().item(), D), device=device
|
||||
@ -192,10 +176,7 @@ class TestPackedToPadded(TestCaseMixin, unittest.TestCase):
|
||||
values_packed_torch.backward(grad_inputs)
|
||||
grad_outputs_torch1 = values_torch.grad
|
||||
grad_outputs_torch2 = TestPackedToPadded.packed_to_padded_python(
|
||||
grad_inputs,
|
||||
mesh_to_faces_packed_first_idx,
|
||||
values.size(1),
|
||||
device=device,
|
||||
grad_inputs, mesh_to_faces_packed_first_idx, values.size(1), device=device
|
||||
)
|
||||
self.assertClose(grad_outputs, grad_outputs_torch1)
|
||||
self.assertClose(grad_outputs, grad_outputs_torch2)
|
||||
@ -219,34 +200,24 @@ class TestPackedToPadded(TestCaseMixin, unittest.TestCase):
|
||||
self._test_padded_to_packed_helper(16, "cuda:0")
|
||||
|
||||
def test_invalid_inputs_shapes(self, device="cuda:0"):
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "input can only be 2-dimensional."
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "input can only be 2-dimensional."):
|
||||
values = torch.rand((100, 50, 2), device=device)
|
||||
first_idxs = torch.tensor([0, 80], dtype=torch.int64, device=device)
|
||||
packed_to_padded(values, first_idxs, 100)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "input can only be 3-dimensional."
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "input can only be 3-dimensional."):
|
||||
values = torch.rand((100,), device=device)
|
||||
first_idxs = torch.tensor([0, 80], dtype=torch.int64, device=device)
|
||||
padded_to_packed(values, first_idxs, 20)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "input can only be 3-dimensional."
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "input can only be 3-dimensional."):
|
||||
values = torch.rand((100, 50, 2, 2), device=device)
|
||||
first_idxs = torch.tensor([0, 80], dtype=torch.int64, device=device)
|
||||
padded_to_packed(values, first_idxs, 20)
|
||||
|
||||
@staticmethod
|
||||
def packed_to_padded_with_init(
|
||||
num_meshes: int,
|
||||
num_verts: int,
|
||||
num_faces: int,
|
||||
num_d: int,
|
||||
device: str = "cpu",
|
||||
num_meshes: int, num_verts: int, num_faces: int, num_d: int, device: str = "cpu"
|
||||
):
|
||||
meshes = TestPackedToPadded.init_meshes(
|
||||
num_meshes, num_verts, num_faces, device
|
||||
@ -268,11 +239,7 @@ class TestPackedToPadded(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
@staticmethod
|
||||
def packed_to_padded_with_init_torch(
|
||||
num_meshes: int,
|
||||
num_verts: int,
|
||||
num_faces: int,
|
||||
num_d: int,
|
||||
device: str = "cpu",
|
||||
num_meshes: int, num_verts: int, num_faces: int, num_d: int, device: str = "cpu"
|
||||
):
|
||||
meshes = TestPackedToPadded.init_meshes(
|
||||
num_meshes, num_verts, num_faces, device
|
||||
|
@ -3,13 +3,12 @@
|
||||
import struct
|
||||
import unittest
|
||||
from io import BytesIO, StringIO
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.io.ply_io import _load_ply_raw, load_ply, save_ply
|
||||
from pytorch3d.utils import torus
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
|
||||
|
||||
class TestMeshPlyIO(TestCaseMixin, unittest.TestCase):
|
||||
def test_raw_load_simple_ascii(self):
|
||||
@ -155,14 +154,7 @@ class TestMeshPlyIO(TestCaseMixin, unittest.TestCase):
|
||||
def test_load_simple_binary(self):
|
||||
for big_endian in [True, False]:
|
||||
verts = (
|
||||
"0 0 0 "
|
||||
"0 0 1 "
|
||||
"0 1 1 "
|
||||
"0 1 0 "
|
||||
"1 0 0 "
|
||||
"1 0 1 "
|
||||
"1 1 1 "
|
||||
"1 1 0"
|
||||
"0 0 0 " "0 0 1 " "0 1 1 " "0 1 0 " "1 0 0 " "1 0 1 " "1 1 1 " "1 1 0"
|
||||
).split()
|
||||
faces = (
|
||||
"4 0 1 2 3 "
|
||||
@ -176,9 +168,7 @@ class TestMeshPlyIO(TestCaseMixin, unittest.TestCase):
|
||||
"3 4 5 1"
|
||||
).split()
|
||||
short_one = b"\00\01" if big_endian else b"\01\00"
|
||||
mixed_data = b"\00\00" b"\03\03" + (
|
||||
short_one + b"\00\01\01\01" b"\00\02"
|
||||
)
|
||||
mixed_data = b"\00\00" b"\03\03" + (short_one + b"\00\01\01\01" b"\00\02")
|
||||
minus_one_data = b"\xff" * 14
|
||||
endian_char = ">" if big_endian else "<"
|
||||
format = (
|
||||
@ -306,9 +296,7 @@ class TestMeshPlyIO(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
lines2 = lines.copy()
|
||||
lines2[8] = "1 2"
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "Inconsistent data for vertex."
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "Inconsistent data for vertex."):
|
||||
_load_ply_raw(StringIO("\n".join(lines2)))
|
||||
|
||||
lines2 = lines[:-1]
|
||||
@ -344,9 +332,7 @@ class TestMeshPlyIO(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
lines2 = lines.copy()
|
||||
lines2.insert(4, "element bad 1")
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "Found an element with no properties."
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "Found an element with no properties."):
|
||||
_load_ply_raw(StringIO("\n".join(lines2)))
|
||||
|
||||
lines2 = lines.copy()
|
||||
@ -369,25 +355,19 @@ class TestMeshPlyIO(TestCaseMixin, unittest.TestCase):
|
||||
lines2 = lines.copy()
|
||||
lines2.insert(4, "property double y")
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "Too little data for an element."
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "Too little data for an element."):
|
||||
_load_ply_raw(StringIO("\n".join(lines2)))
|
||||
|
||||
lines2[-2] = "3.3 4.2"
|
||||
_load_ply_raw(StringIO("\n".join(lines2)))
|
||||
|
||||
lines2[-2] = "3.3 4.3 2"
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "Too much data for an element."
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "Too much data for an element."):
|
||||
_load_ply_raw(StringIO("\n".join(lines2)))
|
||||
|
||||
# Now make the ply file actually be readable as a Mesh
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "The ply file has no face element."
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "The ply file has no face element."):
|
||||
load_ply(StringIO("\n".join(lines)))
|
||||
|
||||
lines2 = lines.copy()
|
||||
@ -398,9 +378,7 @@ class TestMeshPlyIO(TestCaseMixin, unittest.TestCase):
|
||||
lines2.insert(5, "property float z")
|
||||
lines2.insert(5, "property float y")
|
||||
lines2[-2] = "0 0 0"
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "Faces must have at least 3 vertices."
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "Faces must have at least 3 vertices."):
|
||||
load_ply(StringIO("\n".join(lines2)))
|
||||
|
||||
# Good one
|
||||
@ -408,17 +386,11 @@ class TestMeshPlyIO(TestCaseMixin, unittest.TestCase):
|
||||
load_ply(StringIO("\n".join(lines2)))
|
||||
|
||||
@staticmethod
|
||||
def _bm_save_ply(
|
||||
verts: torch.Tensor, faces: torch.Tensor, decimal_places: int
|
||||
):
|
||||
return lambda: save_ply(
|
||||
StringIO(), verts, faces, decimal_places=decimal_places
|
||||
)
|
||||
def _bm_save_ply(verts: torch.Tensor, faces: torch.Tensor, decimal_places: int):
|
||||
return lambda: save_ply(StringIO(), verts, faces, decimal_places=decimal_places)
|
||||
|
||||
@staticmethod
|
||||
def _bm_load_ply(
|
||||
verts: torch.Tensor, faces: torch.Tensor, decimal_places: int
|
||||
):
|
||||
def _bm_load_ply(verts: torch.Tensor, faces: torch.Tensor, decimal_places: int):
|
||||
f = StringIO()
|
||||
save_ply(f, verts, faces, decimal_places)
|
||||
s = f.getvalue()
|
||||
|
@ -1,13 +1,12 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from pytorch3d.structures.pointclouds import Pointclouds
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d.structures.pointclouds import Pointclouds
|
||||
|
||||
|
||||
class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
@ -52,13 +51,11 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
normals_list, features_list = None, None
|
||||
if with_normals:
|
||||
normals_list = [
|
||||
torch.rand((i, 3), device=device, dtype=torch.float32)
|
||||
for i in p
|
||||
torch.rand((i, 3), device=device, dtype=torch.float32) for i in p
|
||||
]
|
||||
if with_features:
|
||||
features_list = [
|
||||
torch.rand((i, channels), device=device, dtype=torch.float32)
|
||||
for i in p
|
||||
torch.rand((i, channels), device=device, dtype=torch.float32) for i in p
|
||||
]
|
||||
|
||||
if lists_to_tensors:
|
||||
@ -68,9 +65,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
if with_features:
|
||||
features_list = torch.stack(features_list)
|
||||
|
||||
return Pointclouds(
|
||||
points_list, normals=normals_list, features=features_list
|
||||
)
|
||||
return Pointclouds(points_list, normals=normals_list, features=features_list)
|
||||
|
||||
def test_simple(self):
|
||||
device = torch.device("cuda:0")
|
||||
@ -81,12 +76,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
device=device,
|
||||
),
|
||||
torch.tensor(
|
||||
[
|
||||
[0.1, 0.3, 0.3],
|
||||
[0.6, 0.7, 0.8],
|
||||
[0.2, 0.3, 0.4],
|
||||
[0.1, 0.5, 0.3],
|
||||
],
|
||||
[[0.1, 0.3, 0.3], [0.6, 0.7, 0.8], [0.2, 0.3, 0.4], [0.1, 0.5, 0.3]],
|
||||
dtype=torch.float32,
|
||||
device=device,
|
||||
),
|
||||
@ -111,9 +101,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
self.assertClose(
|
||||
clouds.cloud_to_packed_first_idx().cpu(), torch.tensor([0, 3, 7])
|
||||
)
|
||||
self.assertClose(
|
||||
clouds.num_points_per_cloud().cpu(), torch.tensor([3, 4, 5])
|
||||
)
|
||||
self.assertClose(clouds.num_points_per_cloud().cpu(), torch.tensor([3, 4, 5]))
|
||||
self.assertClose(
|
||||
clouds.padded_to_packed_idx().cpu(),
|
||||
torch.tensor([0, 1, 2, 5, 6, 7, 8, 10, 11, 12, 13, 14]),
|
||||
@ -129,11 +117,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
"points_padded",
|
||||
"padded_to_packed_idx",
|
||||
]
|
||||
public_normals_getters = [
|
||||
"normals_list",
|
||||
"normals_packed",
|
||||
"normals_padded",
|
||||
]
|
||||
public_normals_getters = ["normals_list", "normals_packed", "normals_padded"]
|
||||
public_features_getters = [
|
||||
"features_list",
|
||||
"features_packed",
|
||||
@ -147,17 +131,13 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
points_data = [torch.zeros((max_len, 3)).uniform_() for i in lengths]
|
||||
normals_data = [torch.zeros((max_len, 3)).uniform_() for i in lengths]
|
||||
features_data = [torch.zeros((max_len, C)).uniform_() for i in lengths]
|
||||
for length, p, n, f in zip(
|
||||
lengths, points_data, normals_data, features_data
|
||||
):
|
||||
for length, p, n, f in zip(lengths, points_data, normals_data, features_data):
|
||||
p[length:] = 0.0
|
||||
n[length:] = 0.0
|
||||
f[length:] = 0.0
|
||||
points_list = [d[:length] for length, d in zip(lengths, points_data)]
|
||||
normals_list = [d[:length] for length, d in zip(lengths, normals_data)]
|
||||
features_list = [
|
||||
d[:length] for length, d in zip(lengths, features_data)
|
||||
]
|
||||
features_list = [d[:length] for length, d in zip(lengths, features_data)]
|
||||
points_packed = torch.cat(points_data)
|
||||
normals_packed = torch.cat(normals_data)
|
||||
features_packed = torch.cat(features_data)
|
||||
@ -173,13 +153,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
("emptylist_emptylist_emptylist", [], [], []),
|
||||
]
|
||||
false_cases_inputs = [
|
||||
(
|
||||
"list_packed",
|
||||
points_list,
|
||||
normals_packed,
|
||||
features_packed,
|
||||
ValueError,
|
||||
),
|
||||
("list_packed", points_list, normals_packed, features_packed, ValueError),
|
||||
("packed_0", points_packed, None, None, ValueError),
|
||||
]
|
||||
|
||||
@ -230,15 +204,11 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
self.assertIsNone(features_padded)
|
||||
for n in range(N):
|
||||
p = points_list[n].shape[0]
|
||||
self.assertClose(
|
||||
points_padded[n, :p, :], points_list[n]
|
||||
)
|
||||
self.assertClose(points_padded[n, :p, :], points_list[n])
|
||||
if with_normals:
|
||||
norms = normals_list[n].shape[0]
|
||||
self.assertEqual(p, norms)
|
||||
self.assertClose(
|
||||
normals_padded[n, :p, :], normals_list[n]
|
||||
)
|
||||
self.assertClose(normals_padded[n, :p, :], normals_list[n])
|
||||
if with_features:
|
||||
f = features_list[n].shape[0]
|
||||
self.assertEqual(p, f)
|
||||
@ -248,9 +218,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
if points_padded.shape[1] > p:
|
||||
self.assertTrue(points_padded[n, p:, :].eq(0).all())
|
||||
if with_features:
|
||||
self.assertTrue(
|
||||
features_padded[n, p:, :].eq(0).all()
|
||||
)
|
||||
self.assertTrue(features_padded[n, p:, :].eq(0).all())
|
||||
self.assertEqual(points_per_cloud[n], p)
|
||||
|
||||
# Check compute packed.
|
||||
@ -272,17 +240,13 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
)
|
||||
if with_normals:
|
||||
self.assertClose(
|
||||
normals_packed[cur : cur + p, :],
|
||||
normals_list[n],
|
||||
normals_packed[cur : cur + p, :], normals_list[n]
|
||||
)
|
||||
if with_features:
|
||||
self.assertClose(
|
||||
features_packed[cur : cur + p, :],
|
||||
features_list[n],
|
||||
features_packed[cur : cur + p, :], features_list[n]
|
||||
)
|
||||
self.assertTrue(
|
||||
packed_to_cloud[cur : cur + p].eq(n).all()
|
||||
)
|
||||
self.assertTrue(packed_to_cloud[cur : cur + p].eq(n).all())
|
||||
self.assertTrue(cloud_to_packed[n] == cur)
|
||||
cur += p
|
||||
|
||||
@ -312,9 +276,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
)[0]
|
||||
points = torch.rand((p, 3), dtype=torch.float32, device=device)
|
||||
normals = torch.rand((p, 3), dtype=torch.float32, device=device)
|
||||
features = torch.rand(
|
||||
(p, C), dtype=torch.float32, device=device
|
||||
)
|
||||
features = torch.rand((p, C), dtype=torch.float32, device=device)
|
||||
else:
|
||||
points = torch.tensor([], dtype=torch.float32, device=device)
|
||||
normals = torch.tensor([], dtype=torch.float32, device=device)
|
||||
@ -331,9 +293,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
if with_features:
|
||||
this_features = features_list
|
||||
clouds = Pointclouds(
|
||||
points=points_list,
|
||||
normals=this_normals,
|
||||
features=this_features,
|
||||
points=points_list, normals=this_normals, features=this_features
|
||||
)
|
||||
points_padded = clouds.points_padded()
|
||||
normals_padded = clouds.normals_padded()
|
||||
@ -346,13 +306,9 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
for n in range(N):
|
||||
p = len(points_list[n])
|
||||
if p > 0:
|
||||
self.assertClose(
|
||||
points_padded[n, :p, :], points_list[n]
|
||||
)
|
||||
self.assertClose(points_padded[n, :p, :], points_list[n])
|
||||
if with_normals:
|
||||
self.assertClose(
|
||||
normals_padded[n, :p, :], normals_list[n]
|
||||
)
|
||||
self.assertClose(normals_padded[n, :p, :], normals_list[n])
|
||||
if with_features:
|
||||
self.assertClose(
|
||||
features_padded[n, :p, :], features_list[n]
|
||||
@ -360,13 +316,9 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
if points_padded.shape[1] > p:
|
||||
self.assertTrue(points_padded[n, p:, :].eq(0).all())
|
||||
if with_normals:
|
||||
self.assertTrue(
|
||||
normals_padded[n, p:, :].eq(0).all()
|
||||
)
|
||||
self.assertTrue(normals_padded[n, p:, :].eq(0).all())
|
||||
if with_features:
|
||||
self.assertTrue(
|
||||
features_padded[n, p:, :].eq(0).all()
|
||||
)
|
||||
self.assertTrue(features_padded[n, p:, :].eq(0).all())
|
||||
self.assertTrue(points_per_cloud[n] == p)
|
||||
|
||||
def test_clone_list(self):
|
||||
@ -379,12 +331,8 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
new_clouds = clouds.clone()
|
||||
|
||||
# Check cloned and original objects do not share tensors.
|
||||
self.assertSeparate(
|
||||
new_clouds.points_list()[0], clouds.points_list()[0]
|
||||
)
|
||||
self.assertSeparate(
|
||||
new_clouds.normals_list()[0], clouds.normals_list()[0]
|
||||
)
|
||||
self.assertSeparate(new_clouds.points_list()[0], clouds.points_list()[0])
|
||||
self.assertSeparate(new_clouds.normals_list()[0], clouds.normals_list()[0])
|
||||
self.assertSeparate(
|
||||
new_clouds.features_list()[0], clouds.features_list()[0]
|
||||
)
|
||||
@ -412,12 +360,8 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
new_clouds = clouds.clone()
|
||||
|
||||
# Check cloned and original objects do not share tensors.
|
||||
self.assertSeparate(
|
||||
new_clouds.points_list()[0], clouds.points_list()[0]
|
||||
)
|
||||
self.assertSeparate(
|
||||
new_clouds.normals_list()[0], clouds.normals_list()[0]
|
||||
)
|
||||
self.assertSeparate(new_clouds.points_list()[0], clouds.points_list()[0])
|
||||
self.assertSeparate(new_clouds.normals_list()[0], clouds.normals_list()[0])
|
||||
self.assertSeparate(
|
||||
new_clouds.features_list()[0], clouds.features_list()[0]
|
||||
)
|
||||
@ -442,9 +386,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
for i in range(N):
|
||||
self.assertClose(cloud1.points_list()[i], cloud2.points_list()[i])
|
||||
self.assertClose(cloud1.normals_list()[i], cloud2.normals_list()[i])
|
||||
self.assertClose(
|
||||
cloud1.features_list()[i], cloud2.features_list()[i]
|
||||
)
|
||||
self.assertClose(cloud1.features_list()[i], cloud2.features_list()[i])
|
||||
has_normals = cloud1.normals_list() is not None
|
||||
self.assertTrue(has_normals == (cloud2.normals_list() is not None))
|
||||
has_features = cloud1.features_list() is not None
|
||||
@ -459,22 +401,13 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
if has_features:
|
||||
self.assertClose(cloud1.features_padded(), cloud2.features_padded())
|
||||
self.assertClose(cloud1.features_packed(), cloud2.features_packed())
|
||||
self.assertClose(cloud1.packed_to_cloud_idx(), cloud2.packed_to_cloud_idx())
|
||||
self.assertClose(
|
||||
cloud1.packed_to_cloud_idx(), cloud2.packed_to_cloud_idx()
|
||||
)
|
||||
self.assertClose(
|
||||
cloud1.cloud_to_packed_first_idx(),
|
||||
cloud2.cloud_to_packed_first_idx(),
|
||||
)
|
||||
self.assertClose(
|
||||
cloud1.num_points_per_cloud(), cloud2.num_points_per_cloud()
|
||||
)
|
||||
self.assertClose(
|
||||
cloud1.packed_to_cloud_idx(), cloud2.packed_to_cloud_idx()
|
||||
)
|
||||
self.assertClose(
|
||||
cloud1.padded_to_packed_idx(), cloud2.padded_to_packed_idx()
|
||||
cloud1.cloud_to_packed_first_idx(), cloud2.cloud_to_packed_first_idx()
|
||||
)
|
||||
self.assertClose(cloud1.num_points_per_cloud(), cloud2.num_points_per_cloud())
|
||||
self.assertClose(cloud1.packed_to_cloud_idx(), cloud2.packed_to_cloud_idx())
|
||||
self.assertClose(cloud1.padded_to_packed_idx(), cloud2.padded_to_packed_idx())
|
||||
self.assertTrue(all(cloud1.valid == cloud2.valid))
|
||||
self.assertTrue(cloud1.equisized == cloud2.equisized)
|
||||
|
||||
@ -482,9 +415,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
def naive_offset(clouds, offsets_packed):
|
||||
new_points_packed = clouds.points_packed() + offsets_packed
|
||||
new_points_list = list(
|
||||
new_points_packed.split(
|
||||
clouds.num_points_per_cloud().tolist(), 0
|
||||
)
|
||||
new_points_packed.split(clouds.num_points_per_cloud().tolist(), 0)
|
||||
)
|
||||
return Pointclouds(
|
||||
points=new_points_list,
|
||||
@ -502,9 +433,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
clouds._compute_padded()
|
||||
clouds.padded_to_packed_idx()
|
||||
|
||||
deform = torch.rand(
|
||||
(all_p, 3), dtype=torch.float32, device=clouds.device
|
||||
)
|
||||
deform = torch.rand((all_p, 3), dtype=torch.float32, device=clouds.device)
|
||||
new_clouds_naive = naive_offset(clouds, deform)
|
||||
|
||||
new_clouds = clouds.offset(deform)
|
||||
@ -521,8 +450,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
clouds.normals_list()[i], new_clouds_naive.normals_list()[i]
|
||||
)
|
||||
self.assertClose(
|
||||
clouds.features_list()[i],
|
||||
new_clouds_naive.features_list()[i],
|
||||
clouds.features_list()[i], new_clouds_naive.features_list()[i]
|
||||
)
|
||||
self.assertCloudsEqual(new_clouds, new_clouds_naive)
|
||||
|
||||
@ -550,15 +478,13 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
new_clouds = clouds.scale(scales)
|
||||
for i in range(N):
|
||||
self.assertClose(
|
||||
scales[i] * clouds.points_list()[i],
|
||||
new_clouds.points_list()[i],
|
||||
scales[i] * clouds.points_list()[i], new_clouds.points_list()[i]
|
||||
)
|
||||
self.assertClose(
|
||||
clouds.normals_list()[i], new_clouds_naive.normals_list()[i]
|
||||
)
|
||||
self.assertClose(
|
||||
clouds.features_list()[i],
|
||||
new_clouds_naive.features_list()[i],
|
||||
clouds.features_list()[i], new_clouds_naive.features_list()[i]
|
||||
)
|
||||
self.assertCloudsEqual(new_clouds, new_clouds_naive)
|
||||
|
||||
@ -576,20 +502,15 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
for i in range(len(clouds)):
|
||||
for n in range(N):
|
||||
self.assertClose(
|
||||
clouds.points_list()[i],
|
||||
new_clouds.points_list()[i * N + n],
|
||||
clouds.points_list()[i], new_clouds.points_list()[i * N + n]
|
||||
)
|
||||
self.assertClose(
|
||||
clouds.normals_list()[i],
|
||||
new_clouds.normals_list()[i * N + n],
|
||||
clouds.normals_list()[i], new_clouds.normals_list()[i * N + n]
|
||||
)
|
||||
self.assertClose(
|
||||
clouds.features_list()[i],
|
||||
new_clouds.features_list()[i * N + n],
|
||||
)
|
||||
self.assertTrue(
|
||||
clouds.valid[i] == new_clouds.valid[i * N + n]
|
||||
clouds.features_list()[i], new_clouds.features_list()[i * N + n]
|
||||
)
|
||||
self.assertTrue(clouds.valid[i] == new_clouds.valid[i * N + n])
|
||||
self.assertAllSeparate(
|
||||
clouds.points_list()
|
||||
+ new_clouds.points_list()
|
||||
@ -627,8 +548,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
"padded_to_packed_idx",
|
||||
]:
|
||||
self.assertClose(
|
||||
getattr(new_cloud, attrib)().cpu(),
|
||||
getattr(cloud, attrib)().cpu(),
|
||||
getattr(new_cloud, attrib)().cpu(), getattr(cloud, attrib)().cpu()
|
||||
)
|
||||
for i in range(len(cloud)):
|
||||
self.assertClose(
|
||||
@ -638,8 +558,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
cloud.normals_list()[i].cpu(), new_cloud.normals_list()[i].cpu()
|
||||
)
|
||||
self.assertClose(
|
||||
cloud.features_list()[i].cpu(),
|
||||
new_cloud.features_list()[i].cpu(),
|
||||
cloud.features_list()[i].cpu(), new_cloud.features_list()[i].cpu()
|
||||
)
|
||||
self.assertTrue(all(cloud.valid.cpu() == new_cloud.valid.cpu()))
|
||||
self.assertTrue(cloud.equisized == new_cloud.equisized)
|
||||
@ -666,8 +585,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
"padded_to_packed_idx",
|
||||
]:
|
||||
self.assertClose(
|
||||
getattr(new_cloud, attrib)().cpu(),
|
||||
getattr(cloud, attrib)().cpu(),
|
||||
getattr(new_cloud, attrib)().cpu(), getattr(cloud, attrib)().cpu()
|
||||
)
|
||||
for i in range(len(cloud)):
|
||||
self.assertClose(
|
||||
@ -677,8 +595,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
cloud.normals_list()[i].cpu(), new_cloud.normals_list()[i].cpu()
|
||||
)
|
||||
self.assertClose(
|
||||
cloud.features_list()[i].cpu(),
|
||||
new_cloud.features_list()[i].cpu(),
|
||||
cloud.features_list()[i].cpu(), new_cloud.features_list()[i].cpu()
|
||||
)
|
||||
self.assertTrue(all(cloud.valid.cpu() == new_cloud.valid.cpu()))
|
||||
self.assertTrue(cloud.equisized == new_cloud.equisized)
|
||||
@ -698,11 +615,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
self.assertEqual(len(split_clouds[1]), 3)
|
||||
self.assertTrue(
|
||||
split_clouds[1].points_list()
|
||||
== [
|
||||
clouds.get_cloud(2)[0],
|
||||
clouds.get_cloud(3)[0],
|
||||
clouds.get_cloud(4)[0],
|
||||
]
|
||||
== [clouds.get_cloud(2)[0], clouds.get_cloud(3)[0], clouds.get_cloud(4)[0]]
|
||||
)
|
||||
|
||||
split_sizes = [2, 0.3]
|
||||
@ -751,9 +664,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
points_padded = clouds.points_padded()
|
||||
points_padded_flat = points_padded.view(-1, 3)
|
||||
|
||||
self.assertClose(
|
||||
points_padded_flat[padded_to_packed_idx], points_packed
|
||||
)
|
||||
self.assertClose(points_padded_flat[padded_to_packed_idx], points_packed)
|
||||
|
||||
idx = padded_to_packed_idx.view(-1, 1).expand(-1, 3)
|
||||
self.assertClose(points_padded_flat.gather(0, idx), points_packed)
|
||||
@ -765,16 +676,13 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
def check_equal(selected, indices):
|
||||
for selectedIdx, index in indices:
|
||||
self.assertClose(
|
||||
selected.points_list()[selectedIdx],
|
||||
clouds.points_list()[index],
|
||||
selected.points_list()[selectedIdx], clouds.points_list()[index]
|
||||
)
|
||||
self.assertClose(
|
||||
selected.normals_list()[selectedIdx],
|
||||
clouds.normals_list()[index],
|
||||
selected.normals_list()[selectedIdx], clouds.normals_list()[index]
|
||||
)
|
||||
self.assertClose(
|
||||
selected.features_list()[selectedIdx],
|
||||
clouds.features_list()[index],
|
||||
selected.features_list()[selectedIdx], clouds.features_list()[index]
|
||||
)
|
||||
|
||||
# int index
|
||||
@ -820,11 +728,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
for with_normfeat in (True, False):
|
||||
for with_new_normfeat in (True, False):
|
||||
clouds = self.init_cloud(
|
||||
N,
|
||||
P,
|
||||
C,
|
||||
with_normals=with_normfeat,
|
||||
with_features=with_normfeat,
|
||||
N, P, C, with_normals=with_normfeat, with_features=with_normfeat
|
||||
)
|
||||
|
||||
num_points_per_cloud = clouds.num_points_per_cloud()
|
||||
@ -843,8 +747,7 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
clouds.points_padded().shape, device=clouds.device
|
||||
)
|
||||
new_normals_list = [
|
||||
new_normals[i, : num_points_per_cloud[i]]
|
||||
for i in range(N)
|
||||
new_normals[i, : num_points_per_cloud[i]] for i in range(N)
|
||||
]
|
||||
feat_shape = [
|
||||
clouds.points_padded().shape[0],
|
||||
@ -853,14 +756,11 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
]
|
||||
new_features = torch.rand(feat_shape, device=clouds.device)
|
||||
new_features_list = [
|
||||
new_features[i, : num_points_per_cloud[i]]
|
||||
for i in range(N)
|
||||
new_features[i, : num_points_per_cloud[i]] for i in range(N)
|
||||
]
|
||||
|
||||
# update
|
||||
new_clouds = clouds.update_padded(
|
||||
new_points, new_normals, new_features
|
||||
)
|
||||
new_clouds = clouds.update_padded(new_points, new_normals, new_features)
|
||||
self.assertIsNone(new_clouds._points_list)
|
||||
self.assertIsNone(new_clouds._points_packed)
|
||||
|
||||
@ -868,13 +768,9 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
self.assertTrue(all(new_clouds.valid == clouds.valid))
|
||||
|
||||
self.assertClose(new_clouds.points_padded(), new_points)
|
||||
self.assertClose(
|
||||
new_clouds.points_packed(), torch.cat(new_points_list)
|
||||
)
|
||||
self.assertClose(new_clouds.points_packed(), torch.cat(new_points_list))
|
||||
for i in range(N):
|
||||
self.assertClose(
|
||||
new_clouds.points_list()[i], new_points_list[i]
|
||||
)
|
||||
self.assertClose(new_clouds.points_list()[i], new_points_list[i])
|
||||
|
||||
if with_new_normfeat:
|
||||
for i in range(N):
|
||||
@ -890,27 +786,22 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
)
|
||||
self.assertClose(new_clouds.features_padded(), new_features)
|
||||
self.assertClose(
|
||||
new_clouds.features_packed(),
|
||||
torch.cat(new_features_list),
|
||||
new_clouds.features_packed(), torch.cat(new_features_list)
|
||||
)
|
||||
else:
|
||||
if with_normfeat:
|
||||
for i in range(N):
|
||||
self.assertClose(
|
||||
new_clouds.normals_list()[i],
|
||||
clouds.normals_list()[i],
|
||||
new_clouds.normals_list()[i], clouds.normals_list()[i]
|
||||
)
|
||||
self.assertClose(
|
||||
new_clouds.features_list()[i],
|
||||
clouds.features_list()[i],
|
||||
new_clouds.features_list()[i], clouds.features_list()[i]
|
||||
)
|
||||
self.assertNotSeparate(
|
||||
new_clouds.normals_list()[i],
|
||||
clouds.normals_list()[i],
|
||||
new_clouds.normals_list()[i], clouds.normals_list()[i]
|
||||
)
|
||||
self.assertNotSeparate(
|
||||
new_clouds.features_list()[i],
|
||||
clouds.features_list()[i],
|
||||
new_clouds.features_list()[i], clouds.features_list()[i]
|
||||
)
|
||||
|
||||
self.assertClose(
|
||||
@ -920,19 +811,16 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
||||
new_clouds.normals_packed(), clouds.normals_packed()
|
||||
)
|
||||
self.assertClose(
|
||||
new_clouds.features_padded(),
|
||||
clouds.features_padded(),
|
||||
new_clouds.features_padded(), clouds.features_padded()
|
||||
)
|
||||
self.assertClose(
|
||||
new_clouds.features_packed(),
|
||||
clouds.features_packed(),
|
||||
new_clouds.features_packed(), clouds.features_packed()
|
||||
)
|
||||
self.assertNotSeparate(
|
||||
new_clouds.normals_padded(), clouds.normals_padded()
|
||||
)
|
||||
self.assertNotSeparate(
|
||||
new_clouds.features_padded(),
|
||||
clouds.features_padded(),
|
||||
new_clouds.features_padded(), clouds.features_padded()
|
||||
)
|
||||
else:
|
||||
self.assertIsNone(new_clouds.normals_list())
|
||||
|
@ -2,8 +2,9 @@
|
||||
|
||||
import functools
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import torch
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d import _C
|
||||
from pytorch3d.renderer.mesh.rasterize_meshes import (
|
||||
rasterize_meshes,
|
||||
@ -12,20 +13,14 @@ from pytorch3d.renderer.mesh.rasterize_meshes import (
|
||||
from pytorch3d.structures import Meshes
|
||||
from pytorch3d.utils import ico_sphere
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
|
||||
|
||||
class TestRasterizeMeshes(TestCaseMixin, unittest.TestCase):
|
||||
def test_simple_python(self):
|
||||
device = torch.device("cpu")
|
||||
self._simple_triangle_raster(
|
||||
rasterize_meshes_python, device, bin_size=-1
|
||||
)
|
||||
self._simple_triangle_raster(rasterize_meshes_python, device, bin_size=-1)
|
||||
self._simple_blurry_raster(rasterize_meshes_python, device, bin_size=-1)
|
||||
self._test_behind_camera(rasterize_meshes_python, device, bin_size=-1)
|
||||
self._test_perspective_correct(
|
||||
rasterize_meshes_python, device, bin_size=-1
|
||||
)
|
||||
self._test_perspective_correct(rasterize_meshes_python, device, bin_size=-1)
|
||||
|
||||
def test_simple_cpu_naive(self):
|
||||
device = torch.device("cpu")
|
||||
@ -350,9 +345,7 @@ class TestRasterizeMeshes(TestCaseMixin, unittest.TestCase):
|
||||
fn1 = functools.partial(rasterize_meshes, meshes1, **kwargs)
|
||||
fn2 = functools.partial(rasterize_meshes_python, meshes2, **kwargs)
|
||||
args = ()
|
||||
self._compare_impls(
|
||||
fn1, fn2, args, args, verts1, verts2, compare_grads=True
|
||||
)
|
||||
self._compare_impls(fn1, fn2, args, args, verts1, verts2, compare_grads=True)
|
||||
|
||||
def test_cpp_vs_cuda_perspective_correct(self):
|
||||
meshes = ico_sphere(2, device=torch.device("cpu"))
|
||||
@ -367,9 +360,7 @@ class TestRasterizeMeshes(TestCaseMixin, unittest.TestCase):
|
||||
fn1 = functools.partial(rasterize_meshes, meshes1, **kwargs)
|
||||
fn2 = functools.partial(rasterize_meshes, meshes2, bin_size=0, **kwargs)
|
||||
args = ()
|
||||
self._compare_impls(
|
||||
fn1, fn2, args, args, verts1, verts2, compare_grads=True
|
||||
)
|
||||
self._compare_impls(fn1, fn2, args, args, verts1, verts2, compare_grads=True)
|
||||
|
||||
def test_cuda_naive_vs_binned_perspective_correct(self):
|
||||
meshes = ico_sphere(2, device=torch.device("cuda"))
|
||||
@ -384,9 +375,7 @@ class TestRasterizeMeshes(TestCaseMixin, unittest.TestCase):
|
||||
fn1 = functools.partial(rasterize_meshes, meshes1, bin_size=0, **kwargs)
|
||||
fn2 = functools.partial(rasterize_meshes, meshes2, bin_size=8, **kwargs)
|
||||
args = ()
|
||||
self._compare_impls(
|
||||
fn1, fn2, args, args, verts1, verts2, compare_grads=True
|
||||
)
|
||||
self._compare_impls(fn1, fn2, args, args, verts1, verts2, compare_grads=True)
|
||||
|
||||
def _compare_impls(
|
||||
self,
|
||||
@ -433,9 +422,7 @@ class TestRasterizeMeshes(TestCaseMixin, unittest.TestCase):
|
||||
grad_verts2 = grad_var2.grad.data.clone().cpu()
|
||||
self.assertClose(grad_verts1, grad_verts2, rtol=1e-3)
|
||||
|
||||
def _test_perspective_correct(
|
||||
self, rasterize_meshes_fn, device, bin_size=None
|
||||
):
|
||||
def _test_perspective_correct(self, rasterize_meshes_fn, device, bin_size=None):
|
||||
# fmt: off
|
||||
verts = torch.tensor([
|
||||
[-0.4, -0.4, 10], # noqa: E241, E201
|
||||
@ -542,12 +529,8 @@ class TestRasterizeMeshes(TestCaseMixin, unittest.TestCase):
|
||||
zbuf_f_bary = w0_f * z0 + w1_f * z1 + w2_f * z2
|
||||
zbuf_t_bary = w0_t * z0 + w1_t * z1 + w2_t * z2
|
||||
mask = idx_expected != -1
|
||||
zbuf_f_bary_diff = (
|
||||
(zbuf_f_bary[mask] - zbuf_f_expected[mask]).abs().max()
|
||||
)
|
||||
zbuf_t_bary_diff = (
|
||||
(zbuf_t_bary[mask] - zbuf_t_expected[mask]).abs().max()
|
||||
)
|
||||
zbuf_f_bary_diff = (zbuf_f_bary[mask] - zbuf_f_expected[mask]).abs().max()
|
||||
zbuf_t_bary_diff = (zbuf_t_bary[mask] - zbuf_t_expected[mask]).abs().max()
|
||||
self.assertLess(zbuf_f_bary_diff, 1e-4)
|
||||
self.assertLess(zbuf_t_bary_diff, 1e-4)
|
||||
|
||||
@ -719,9 +702,7 @@ class TestRasterizeMeshes(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
# k = 1, second closest point.
|
||||
expected_p2face_k1 = expected_p2face_k0.clone()
|
||||
expected_p2face_k1[0, :] = (
|
||||
torch.ones_like(expected_p2face_k1[0, :]) * -1
|
||||
)
|
||||
expected_p2face_k1[0, :] = torch.ones_like(expected_p2face_k1[0, :]) * -1
|
||||
|
||||
# fmt: off
|
||||
expected_p2face_k1[1, :] = torch.tensor(
|
||||
@ -763,9 +744,7 @@ class TestRasterizeMeshes(TestCaseMixin, unittest.TestCase):
|
||||
# Coordinate conventions +Y up, +Z in, +X left
|
||||
if bin_size == -1:
|
||||
# simple python, no bin_size
|
||||
p2face, zbuf, bary, pix_dists = raster_fn(
|
||||
meshes, image_size, 0.0, 2
|
||||
)
|
||||
p2face, zbuf, bary, pix_dists = raster_fn(meshes, image_size, 0.0, 2)
|
||||
else:
|
||||
p2face, zbuf, bary, pix_dists = raster_fn(
|
||||
meshes, image_size, 0.0, 2, bin_size
|
||||
@ -914,9 +893,7 @@ class TestRasterizeMeshes(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
# Expected faces using axes convention +Y down, + X right, + Z in
|
||||
bin_faces_expected = (
|
||||
torch.ones(
|
||||
(1, 2, 2, max_faces_per_bin), dtype=torch.int32, device=device
|
||||
)
|
||||
torch.ones((1, 2, 2, max_faces_per_bin), dtype=torch.int32, device=device)
|
||||
* -1
|
||||
)
|
||||
bin_faces_expected[0, 0, 0, 0] = torch.tensor([1])
|
||||
@ -979,12 +956,7 @@ class TestRasterizeMeshes(TestCaseMixin, unittest.TestCase):
|
||||
|
||||
def rasterize():
|
||||
rasterize_meshes(
|
||||
meshes_batch,
|
||||
image_size,
|
||||
blur_radius,
|
||||
8,
|
||||
bin_size,
|
||||
max_faces_per_bin,
|
||||
meshes_batch, image_size, blur_radius, 8, bin_size, max_faces_per_bin
|
||||
)
|
||||
torch.cuda.synchronize()
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from common_testing import TestCaseMixin
|
||||
from pytorch3d import _C
|
||||
from pytorch3d.renderer.points.rasterize_points import (
|
||||
rasterize_points,
|
||||
@ -12,8 +13,6 @@ from pytorch3d.renderer.points.rasterize_points import (
|
||||
)
|
||||
from pytorch3d.structures.pointclouds import Pointclouds
|
||||
|
||||
from common_testing import TestCaseMixin
|
||||
|
||||
|
||||
class TestRasterizePoints(TestCaseMixin, unittest.TestCase):
|
||||
def test_python_simple_cpu(self):
|
||||
@ -38,9 +37,7 @@ class TestRasterizePoints(TestCaseMixin, unittest.TestCase):
|
||||
self._test_behind_camera(rasterize_points, torch.device("cpu"))
|
||||
|
||||
def test_cuda_behind_camera(self):
|
||||
self._test_behind_camera(
|
||||
rasterize_points, torch.device("cuda"), bin_size=0
|
||||
)
|
||||
self._test_behind_camera(rasterize_points, torch.device("cuda"), bin_size=0)
|
||||
|
||||
def test_cpp_vs_naive_vs_binned(self):
|
||||
# Make sure that the backward pass runs for all pathways
|
||||
@ -167,20 +164,8 @@ class TestRasterizePoints(TestCaseMixin, unittest.TestCase):
|
||||
points_cuda = points_cpu.cuda().detach().requires_grad_(True)
|
||||
pointclouds_cpu = Pointclouds(points=points_cpu)
|
||||
pointclouds_cuda = Pointclouds(points=points_cuda)
|
||||
args_cpu = (
|
||||
pointclouds_cpu,
|
||||
image_size,
|
||||
radius,
|
||||
points_per_pixel,
|
||||
bin_size,
|
||||
)
|
||||
args_cuda = (
|
||||
pointclouds_cuda,
|
||||
image_size,
|
||||
radius,
|
||||
points_per_pixel,
|
||||
bin_size,
|
||||
)
|
||||
args_cpu = (pointclouds_cpu, image_size, radius, points_per_pixel, bin_size)
|
||||
args_cuda = (pointclouds_cuda, image_size, radius, points_per_pixel, bin_size)
|
||||
self._compare_impls(
|
||||
rasterize_points,
|
||||
rasterize_points,
|
||||
@ -332,9 +317,7 @@ class TestRasterizePoints(TestCaseMixin, unittest.TestCase):
|
||||
], device=device)
|
||||
# fmt: on
|
||||
|
||||
dists1_expected = torch.zeros(
|
||||
(5, 5, 2), dtype=torch.float32, device=device
|
||||
)
|
||||
dists1_expected = torch.zeros((5, 5, 2), dtype=torch.float32, device=device)
|
||||
# fmt: off
|
||||
dists1_expected[:, :, 0] = torch.tensor([
|
||||
[-1.00, -1.00, 0.16, -1.00, -1.00], # noqa: E241
|
||||
|
@ -1,22 +1,17 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image
|
||||
|
||||
from pytorch3d.renderer.cameras import (
|
||||
OpenGLPerspectiveCameras,
|
||||
look_at_view_transform,
|
||||
)
|
||||
from pytorch3d.renderer.mesh.rasterizer import (
|
||||
MeshRasterizer,
|
||||
RasterizationSettings,
|
||||
)
|
||||
from pytorch3d.renderer.cameras import OpenGLPerspectiveCameras, look_at_view_transform
|
||||
from pytorch3d.renderer.mesh.rasterizer import MeshRasterizer, RasterizationSettings
|
||||
from pytorch3d.utils.ico_sphere import ico_sphere
|
||||
|
||||
|
||||
DATA_DIR = Path(__file__).resolve().parent / "data"
|
||||
DEBUG = False # Set DEBUG to true to save outputs from the tests.
|
||||
|
||||
@ -52,9 +47,7 @@ class TestMeshRasterizer(unittest.TestCase):
|
||||
)
|
||||
|
||||
# Init rasterizer
|
||||
rasterizer = MeshRasterizer(
|
||||
cameras=cameras, raster_settings=raster_settings
|
||||
)
|
||||
rasterizer = MeshRasterizer(cameras=cameras, raster_settings=raster_settings)
|
||||
|
||||
####################################
|
||||
# 1. Test rasterizing a single mesh
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user