Skip to content

Commit 27efa0f

Browse files
shumashMaria Masha Shugrina
and
Maria Masha Shugrina
authored
- Added some testing utilities and corresponding tests for these utilities (#718)
- Added explicit size checks to usd i/o tests; improved tests to not rely on test order execution; made some changes to USD reading code that improves logic flow; fixed documentation error on export_mesh in USD. - Fixed bug in get_scene_paths and added a test - Added three additional test meshes. Started a convention for keeping useful data in one place and referencing it from appropriately named test subfolder. - Added more robust tests for USD meshes; fixed at least one bug; added a partial refactor to usd/mesh.py (not integrated with core readers yet). Removed accidentally added file - Normal consistency spot checks between OBJ and USD now pass; changed small USD samples to human-readable usda version. - Fixed a bug in rtol/atol in a testing function (backward compatible); small improvement to debug logging function - Non-backward compatible changes to USD material import: - stop always prepending material list with None - always output material_order, even if only one material is bound (after all it could be not bound) - Added extensive OBJ/USD consistency checks that at least verify material_order is read consistently. - Added export materials + reimport materials test for an existing test case to ensure read-write-read cycle is not broken. - Adapted obj/usd consistency test to work with multi-mesh USDs. - Fixed a bug: USD transform should apply not only to vertices, but also normals. Signed-off-by: Maria Masha Shugrina <[email protected]> Co-authored-by: Maria Masha Shugrina <[email protected]>
1 parent 7721cee commit 27efa0f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+30616
-204
lines changed

docs/notes/installation.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ For an exhaustive check, install testing dependencies and run tests as follows:
152152
$ pip install -r tools/ci_requirements.txt
153153
$ export CI='true' # on Linux
154154
$ set CI='true' # on Windows
155-
$ pytest tests/python/
155+
$ pytest --import-mode=importlib -s tests/python/
156156
157157
.. Note::
158158
These tests rely on CUDA operations and will fail if you installed on CPU only, where not all functionality is available.

kaolin/io/usd/mesh.py

+179-39
Large diffs are not rendered by default.

kaolin/io/usd/utils.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ def get_scene_paths(file_path_or_stage, scene_path_regex=None, prim_types=None,
5656
Path to usd file (\*.usd, \*.usda) or :class:`Usd.Stage`.
5757
scene_path_regex (str, optional):
5858
Optional regular expression used to select returned scene paths.
59-
prim_types (list of str, optional):
60-
Optional list of valid USD Prim types used to select scene paths.
59+
prim_types (list of str, str, optional):
60+
Optional list of valid USD Prim types used to select scene paths, or a single USD Prim type string.
6161
conditional (function path: Bool): Custom conditionals to check
6262
6363
Returns:
@@ -78,6 +78,8 @@ def get_scene_paths(file_path_or_stage, scene_path_regex=None, prim_types=None,
7878
if scene_path_regex is None:
7979
scene_path_regex = '.*'
8080
if prim_types is not None:
81+
if isinstance(prim_types, str):
82+
prim_types = [prim_types]
8183
prim_types = [pt.lower() for pt in prim_types]
8284

8385
scene_paths = []

kaolin/utils/testing.py

+85-15
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16-
import functools
16+
import copy
1717
import collections
18+
import functools
19+
import logging
1820
import numpy as np
1921
import torch
2022

@@ -68,6 +70,10 @@ def check_tensor(tensor, shape=None, dtype=None, device=None, throw=True):
6870
if a dimension is set at ``None`` then it's not verified.
6971
dtype (torch.dtype, optional): the expected dtype.
7072
device (torch.device, optional): the expected device.
73+
throw (bool): if true (default), will throw if checks fail
74+
75+
Return:
76+
(bool) True if checks pass
7177
"""
7278
if shape is not None:
7379
if len(shape) != tensor.ndim:
@@ -117,7 +123,7 @@ def check_packed_tensor(tensor, total_numel=None, last_dim=None, dtype=None, dev
117123
return False
118124
return True
119125

120-
def check_padded_tensor(tensor, padding_value=None, shape_per_tensor=None,
126+
def check_padded_tensor(tensor, padding_value=None, shape_per_tensor=None,
121127
batch_size=None, max_shape=None, last_dim=None,
122128
dtype=None, device=None, throw=True):
123129
"""Check if :ref:`padded tensor<padded>` is valid given set of criteria.
@@ -135,7 +141,7 @@ def check_padded_tensor(tensor, padding_value=None, shape_per_tensor=None,
135141
136142
Return:
137143
(bool): status of the check.
138-
"""
144+
"""
139145
if not check_tensor(tensor, dtype=dtype, device=device, throw=throw):
140146
return False
141147
if shape_per_tensor is not None:
@@ -274,48 +280,112 @@ def _get_details_str():
274280
(_get_stats_str() if print_stats else ''),
275281
(_get_details_str() if detailed else '')))
276282

277-
def contained_torch_equal(elem, other):
278-
"""Check for equality of two objects potentially containing tensors.
283+
def contained_torch_equal(elem, other, approximate=False, **allclose_args):
284+
"""Check for equality (or allclose if approximate) of two objects potentially containing tensors.
279285
280286
:func:`torch.equal` do not support data structure like dictionary / arrays
281287
and `==` is ambiguous on :class:`torch.Tensor`.
282288
This class will try to apply recursion through :class:`collections.abc.Mapping`,
283289
:class:`collections.abc.Sequence`, :func:`torch.equal` if the objects are `torch.Tensor`,
284290
of else `==` operator.
285-
291+
286292
Args:
287-
elem (object): The first object
288-
other (object): The other object to compare to ``elem``
293+
elem (object, dict, list, tuple): The first object
294+
other (object, dict, list, tuple): The other object to compare to ``elem``
295+
approximate (bool): if requested will use allclose for comparison instead (default=False)
296+
allclose_args: arguments to `torch.allclose` if approximate comparison requested
289297
290298
Return (bool): the comparison result
291299
"""
292300
elem_type = type(elem)
293301
if elem_type != type(other):
294302
return False
295303

304+
def _tensor_compare(a, b):
305+
if not approximate:
306+
return torch.equal(a, b)
307+
else:
308+
return torch.allclose(a, b, **allclose_args)
309+
310+
def _number_compare(a, b):
311+
return _tensor_compare(torch.tensor([a]), torch.tensor([b]))
312+
313+
recursive_args = copy.copy(allclose_args)
314+
recursive_args['approximate'] = approximate
315+
296316
if isinstance(elem, torch.Tensor):
297-
return torch.equal(elem, other)
317+
return _tensor_compare(elem, other)
298318
elif isinstance(elem, str):
299319
return elem == other
320+
elif isinstance(elem, float):
321+
return _number_compare(elem, other)
300322
elif isinstance(elem, collections.abc.Mapping):
301323
if elem.keys() != other.keys():
302324
return False
303-
return all(contained_torch_equal(elem[key], other[key]) for key in elem)
325+
return all(contained_torch_equal(elem[key], other[key], **recursive_args) for key in elem)
304326
elif isinstance(elem, tuple) and hasattr(elem, '_fields'): # namedtuple
305-
if set(elem._fields()) != set(other._fields()):
327+
if set(elem._fields) != set(other._fields):
306328
return False
307329
return all(contained_torch_equal(
308-
getattr(elem, f), getattr(other, f)) for f in elem._fields()
330+
getattr(elem, f), getattr(other, f), **recursive_args) for f in elem._fields
309331
)
310332
elif isinstance(elem, collections.abc.Sequence):
311333
if len(elem) != len(other):
312334
return False
313-
return all(contained_torch_equal(a, b) for a, b in zip(elem, other))
335+
return all(contained_torch_equal(a, b, **recursive_args) for a, b in zip(elem, other))
314336
else:
315337
return elem == other
316338

317339
def check_allclose(tensor, other, rtol=1e-5, atol=1e-8, equal_nan=False):
318-
if not torch.allclose(tensor, other, atol, rtol, equal_nan):
319-
diff_idx = torch.where(~torch.isclose(tensor, other, atol, rtol, equal_nan))
340+
if not torch.allclose(tensor, other, atol=atol, rtol=rtol, equal_nan=equal_nan):
341+
diff_idx = torch.where(~torch.isclose(tensor, other, atol=atol, rtol=rtol, equal_nan=equal_nan))
320342
raise ValueError(f"Tensors are not close on indices {diff_idx}:",
321343
f"Example values: {tensor[diff_idx][:10]} vs {other[diff_idx][:10]}.")
344+
345+
def check_tensor_attribute_shapes(container, throw=True, **attribute_info):
346+
"""Checks shape on all specified attributes of the container.
347+
348+
Args:
349+
container (dict, tuple, object): container with named attributes to be tested
350+
throw (bool): if true (default), will throw error on first check that fails
351+
attribute_info: named attribute=shape values, where shape can be list or tuple (see `check_tensor`)
352+
353+
Return:
354+
(bool) True if checks pass
355+
"""
356+
def _get_item(container, attr):
357+
if isinstance(container, collections.abc.Mapping):
358+
return container[attr]
359+
else:
360+
return getattr(container, attr)
361+
362+
success = True
363+
for k, shape in attribute_info.items():
364+
val = _get_item(container, k)
365+
if not check_tensor(val, shape=shape, throw=False):
366+
success = False
367+
message = f'Attribute {k} has shape {val.shape} (expected {shape})'
368+
if throw:
369+
raise ValueError(message)
370+
else:
371+
logging.error(message)
372+
return success
373+
374+
375+
def print_namedtuple_attributes(ntuple, name='', **tensor_info_args):
376+
print_dict_attributes(ntuple._asdict(), name=name, **tensor_info_args)
377+
378+
379+
def print_dict_attributes(in_dict, name='', **tensor_info_args):
380+
name_info = '' if len(name) == 0 else f' of {name}'
381+
print(f'\nAttributes{name_info}:')
382+
for k, v in in_dict.items():
383+
if torch.is_tensor(v):
384+
tinfo = tensor_info(v, **tensor_info_args)
385+
elif isinstance(v, (str, int, float)):
386+
tinfo = v
387+
elif isinstance(v, collections.abc.Sequence):
388+
tinfo = f'{type(v)} of length {len(v)}'
389+
else:
390+
tinfo = type(v)
391+
print(f' {k}: {tinfo}')

sample_data/meshes/README.md

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Sample Meshes
2+
3+
This directory contains sample meshes that might be useful across the project, in both
4+
tests and examples. These are simple test cases for stress-testing and prototyping
5+
and are not meant to be high-quality 3D examples.
6+
7+
## Flat-shaded Ico Sphere
8+
9+
**Filenames**: [ico_flat.obj](ico_flat.obj), [ico_flat.usda](ico_flat.usda).
10+
11+
**Source**: this mesh is an ico sphere that was authored in Blender with flat shading.
12+
13+
**Formats**: was exported as both `obj` and `usda` (ascii) from Blender.
14+
15+
**Sanity checks**: displays correctly as `obj` and `usda` imported into Blender3.1 and as `usda` imported into [NVIDIA Omniverse](https://www.nvidia.com/en-us/omniverse/).
16+
17+
<img width="30%" style="padding:0 20px 0 0; float: left" src="renders/ico_flat.jpg">
18+
19+
**Attributes** of the single mesh in this file:
20+
* Vertices: 42
21+
* Faces: 80 triangles
22+
* Materials: one simple material applied to all faces
23+
* Normals: for flat shading (same normal for all vertices of a face)
24+
* UVs
25+
26+
<div style="clear:both"></div>
27+
28+
## Smooth-shaded Ico Sphere
29+
30+
**Filenames**: [ico_smooth.obj](ico_smooth.obj), [ico_smooth.usda](ico_smooth.usda).
31+
32+
**Source**: this mesh is an ico sphere that was authored in Blender with smooth shading.
33+
34+
**Formats**: was exported as both `obj` and `usda` (ascii) from Blender.
35+
36+
**Sanity checks**: displays correctly as `obj` and `usda` imported into Blender3.1 and as `usda` imported into [NVIDIA Omniverse](https://www.nvidia.com/en-us/omniverse/).
37+
38+
<img width="30%" style="padding:0 20px 0 0; float: left" src="renders/ico_smooth.jpg">
39+
40+
**Attributes** of the single mesh in this file:
41+
* Vertices: 42
42+
* Faces: 80 triangles
43+
* Materials: one simple material applied to all faces
44+
* Normals: for smooth shading
45+
* UVs
46+
47+
<div style="clear:both"></div>
48+
49+
## Textured Fox
50+
51+
**Filenames:** [fox.obj](fox.obj), [fox.usdc](fox.usdc).
52+
53+
**Source:** this file is a simiplified version of TurboSquid ID 1073771, which NVIDIA has licensed with distribution rights. *By using this TurboSquid model, you agree that you will only use the content for research purposes only. You may not redistribute this model.*
54+
55+
**Formats**: the original format is `obj`, which was converted to `usdc` (binary) using Blender exporter.
56+
57+
**Sanity checks**: displays correctly as `obj` and `usdc` imported into Blender3.1 and as `usdc` imported into [NVIDIA Omniverse](https://www.nvidia.com/en-us/omniverse/).
58+
59+
<img width="30%" style="padding:0 20px 0 0; float: left" src="renders/fox.jpg">
60+
61+
**Attributes** of the single mesh in this file:
62+
* Vertices: 5002
63+
* Faces: 10000 triangles
64+
* Materials: one simple material with texture applied to all faces
65+
* Normals: for smooth shading
66+
* UVs.
67+
68+
<div style="clear:both"></div>
69+
70+
## Multi-Material Pizza
71+
72+
**Filenames:** [pizza.obj](pizza.obj), [pizza.usda](pizza.usda).
73+
74+
**Source:** this file was authored in Blender with pizza texture taken from a [royalty-free photo by Rene Strgar](https://www.pexels.com/photo/italian-style-pizza-13814644/).
75+
76+
**Formats**: was exported as both `obj` and `usda` (ascii) from Blender.
77+
78+
**Sanity checks**: displays correctly as `obj` (not usd) imported into Blender3.1 and as `usda` imported into [NVIDIA Omniverse](https://www.nvidia.com/en-us/omniverse/).
79+
80+
<img width="30%" style="padding:0 20px 0 0; float: left" src="renders/pizza.jpg">
81+
82+
**Attributes** of the single mesh in this file:
83+
* Vertices: 482
84+
* Faces: 960 triangles
85+
* Materials: one simple material and one texture material applied to groups of faces
86+
* Normals: for smooth shading
87+
* UVs
88+
89+
<div style="clear:both"></div>

sample_data/meshes/fox.mtl

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Blender MTL File: 'None'
2+
# Material Count: 1
3+
4+
newmtl material_0
5+
Ns 0.000000
6+
Ka 1.000000 1.000000 1.000000
7+
Kd 0.800000 0.800000 0.800000
8+
Ks 1.000000 1.000000 1.000000
9+
Ke 0.000000 0.000000 0.000000
10+
Ni 1.450000
11+
d 1.000000
12+
illum 2
13+
map_Kd textures/fox.jpg

0 commit comments

Comments
 (0)