-
Notifications
You must be signed in to change notification settings - Fork 23
Added support for loading native object for openvdb #170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
0e31843
eb448e3
d660aef
faf508b
5d0dfac
099ea1a
b5a40a8
79fc2b6
d1c8d41
f7c2f9e
824b4af
3de5c04
3f0cc9b
b92ba88
86b6acf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -109,20 +109,21 @@ | |
| @dataclass | ||
| class DownCastTo: | ||
| """:func:`~dataclasses.dataclass` decorator serving as a marker for a downcast. | ||
|
|
||
| This function is used to create a proxy for an OpenVDB grid type. | ||
| The field :attr:`gridType` contains the OpenVDB grid type that it represents. | ||
| :meth:`OpenVDBField._get_best_grid_type` selects a OpenVDB grid that best matches | ||
| the numpy dtype of the data but in some cases, only target OpenVDB grid types are | ||
| available that loose precision. In this case, this class wraps the orginal OpenVDB | ||
| class to indicate that the downcast. For example, :: | ||
|
|
||
| np.dtype("int32"): ["Int32Grid", DownCastTo("FloatGrid")] | ||
|
|
||
| indicates that NumPy int32 data should be represented by a :class:`openvdb.Int32Grid` | ||
| but if this is not available, a :class:`openvdb.FloatGrid` is used instead, | ||
| which, however, is only able to represent a subset of all 32-bit integers. | ||
| """ | ||
|
|
||
| gridType: str | ||
|
|
||
|
|
||
|
|
@@ -142,7 +143,7 @@ class OpenVDBField(object): | |
|
|
||
| import gridData.OpenVDB as OpenVDB | ||
|
|
||
| vdb_field = OpenVDB.OpenVDBField(grid=np.ones((3, 4, 5)), | ||
| vdb_field = OpenVDB.OpenVDBField(grid=np.ones((3, 4, 5)), | ||
| origin=np.array([1.5, 0, 0]), | ||
| delta=np.array([0.5, 0.5, 0.25]), | ||
| name='density') | ||
|
|
@@ -154,6 +155,22 @@ class OpenVDBField(object): | |
| g.export('output.vdb', format='vdb') | ||
| """ | ||
|
|
||
| # dtype maps | ||
| _DATATYPES = { | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
| np.dtype("bool"): ["BoolGrid"], | ||
| np.dtype("int8"): ["Int32Grid", "FloatGrid"], | ||
| np.dtype("uint8"): ["Int32Grid", "FloatGrid"], | ||
| np.dtype("int16"): ["Int32Grid", "FloatGrid"], | ||
| np.dtype("uint16"): ["Int32Grid", "FloatGrid"], | ||
| np.dtype("int32"): ["Int32Grid", DownCastTo("FloatGrid")], | ||
| np.dtype("uint32"): [DownCastTo("Int32Grid"), DownCastTo("FloatGrid")], | ||
| np.dtype("int64"): ["Int64Grid", DownCastTo("FloatGrid")], | ||
| np.dtype("uint64"): ["Int64Grid", DownCastTo("FloatGrid")], | ||
| np.dtype("float16"): ["HalfGrid", "FloatGrid"], | ||
| np.dtype("float32"): ["FloatGrid"], | ||
| np.dtype("float64"): ["DoubleGrid", DownCastTo("FloatGrid")], | ||
| } | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
|
|
||
| def __init__( | ||
| self, | ||
| grid=None, | ||
|
|
@@ -205,8 +222,13 @@ def __init__( | |
| self.metadata = {} | ||
|
|
||
| if grid is not None: | ||
| self._populate(grid, origin, delta) | ||
| self.vdb_grid = self._create_openvdb_grid() | ||
| if isinstance(grid, vdb.GridBase): | ||
| self.vdb_grid = grid | ||
| self._extract_from_vdb_grid() | ||
| else: | ||
| self._populate(grid, origin, delta) | ||
| self.vdb_grid = self._create_openvdb_grid() | ||
|
|
||
| else: | ||
| self.grid = None | ||
| self.origin = None | ||
|
|
@@ -272,6 +294,57 @@ def native(self): | |
| """ | ||
| return self.vdb_grid | ||
|
|
||
| def _extract_from_vdb_grid(self): | ||
| """Extract numpy array, origin, delta from stored VDB grid. | ||
|
|
||
| This method converts the sparse VDB grid to a dense numpy array | ||
| and extracts the transform information. | ||
| """ | ||
| for key in self.vdb_grid.metadata: | ||
| try: | ||
| self.metadata[key] = self.vdb_grid[key] | ||
| except (TypeError, ValueError): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Under which conditions does that fail? I am always suspicious of try/except that passes.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. vec3f can be one of the example. But I can create a path for a warning to be raised for this |
||
| pass | ||
|
|
||
| transformation = self.vdb_grid.transform | ||
|
|
||
| v_origin = np.array(transformation.indexToWorld([0, 0, 0])) | ||
| v_delta = np.array(transformation.indexToWorld([1, 1, 1])) - v_origin | ||
|
|
||
| self.origin = v_origin | ||
| self.delta = v_delta | ||
|
|
||
| dtype = np.dtype("float32") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's cleaner to set the dtype to the default in an for numpy_dtype, vdb_names in self._DATATYPES.items():
name_dtype = vdb_names[0]
...
if vdb_class_name == canonical_name:
dtype = numpy_dtype
break
else:
# could not find a matching dtype, use default
dtype = np.float32Or maybe should we use float64? @BradyAJohnston @PardhavMaradani do you have an opinion?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should also log a warning if you encounter an unknown type and use the default. |
||
| vdb_class_name = type(self.vdb_grid).__name__ | ||
| for numpy_dtype, vdb_names in self._DATATYPES.items(): | ||
| name_dtype = vdb_names[0] | ||
| canonical_name = ( | ||
| name_dtype.gridType | ||
| if isinstance(name_dtype, DownCastTo) | ||
| else name_dtype | ||
| ) | ||
|
|
||
| if vdb_class_name == canonical_name: | ||
| dtype = numpy_dtype | ||
| break | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic is flawed. You are picking the first match to a value in the Instead you must define an appropriate mapping from VDB to np.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep, I will try to redo that
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please once relook at the logic |
||
|
|
||
| bbox = self.vdb_grid.evalActiveVoxelBoundingBox() | ||
|
|
||
| if bbox is None or bbox[0] == bbox[1]: | ||
| self.grid = np.zeros((0, 0, 0), dtype=dtype) | ||
| return | ||
|
spyke7 marked this conversation as resolved.
Outdated
|
||
|
|
||
| shape = tuple(np.array(bbox[1]) - np.array(bbox[0]) + 1) | ||
|
|
||
| self.grid = np.zeros(shape, dtype=dtype) | ||
| print(dtype) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. still a print there! |
||
| self.vdb_grid.copyToArray(self.grid, ijk=bbox[0]) | ||
|
|
||
| if not np.all(np.array(bbox[0]) == 0): | ||
| self.origin = np.array( | ||
| transformation.indexToWorld(np.array(bbox[0]).tolist()) | ||
| ) | ||
|
|
||
| def _populate(self, grid, origin, delta): | ||
| """Populate the field with grid data. | ||
|
|
||
|
|
@@ -331,23 +404,8 @@ def _get_best_grid_type(self): | |
| TypeError | ||
| If dtype is not supported or no suitable grid type is available | ||
| """ | ||
| datatypes = { | ||
| np.dtype("bool"): ["BoolGrid"], | ||
| np.dtype("int8"): ["Int32Grid", "FloatGrid"], | ||
| np.dtype("uint8"): ["Int32Grid", "FloatGrid"], | ||
| np.dtype("int16"): ["Int32Grid", "FloatGrid"], | ||
| np.dtype("uint16"): ["Int32Grid", "FloatGrid"], | ||
| np.dtype("int32"): ["Int32Grid", DownCastTo("FloatGrid")], | ||
| np.dtype("uint32"): [DownCastTo("Int32Grid"), DownCastTo("FloatGrid")], | ||
| np.dtype("int64"): ["Int64Grid", DownCastTo("FloatGrid")], | ||
| np.dtype("uint64"): ["Int64Grid", DownCastTo("FloatGrid")], | ||
| np.dtype("float16"): ["HalfGrid", "FloatGrid"], | ||
| np.dtype("float32"): ["FloatGrid"], | ||
| np.dtype("float64"): ["DoubleGrid", DownCastTo("FloatGrid")], | ||
| } | ||
|
|
||
| try: | ||
| vdb_gridtypes = datatypes[self.grid.dtype] | ||
| vdb_gridtypes = self._DATATYPES[self.grid.dtype] | ||
| except KeyError: | ||
| raise TypeError(f"Data type {self.grid.dtype} not supported for VDB") | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -249,6 +249,21 @@ def __init__(self, grid=None, edges=None, origin=None, delta=None, | |
| self.interpolation_cval = None # default to using min(grid) | ||
|
|
||
| if grid is not None: | ||
| try: | ||
| # if a openvdb native grid is passed | ||
| import openvdb as vdb | ||
| if isinstance(grid, vdb.GridBase): | ||
| vdb_field = OpenVDB.OpenVDBField(grid=grid) | ||
| self.metadata = vdb_field.metadata | ||
| self._load( | ||
| grid=vdb_field.grid, | ||
| origin=vdb_field.origin, | ||
| delta=vdb_field.delta, | ||
| metadata=vdb_field.metadata, | ||
| ) | ||
| return | ||
| except ImportError: | ||
| pass | ||
|
Comment on lines
+252
to
+266
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the right idea. This (together with the filename/array code below) is quite clunky. For each additional format we will need to add another try/except. We need to come up with a more general solution, e.g., a dict-based dispatch lookup or a dispatcher function that calls the correct setup, based on the detected format.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. jus like |
||
| if isinstance(grid, str): | ||
| # can probably safely try to load() it... | ||
| filename = grid | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -392,6 +392,20 @@ def test_from_native_grid_shape_values_and_dimension(self, grid345): | |
| world = native_grid.transform.indexToWorld((0, 0, 0)) | ||
| assert_allclose([world[0], world[1], world[2]], g.origin, rtol=1e-5) | ||
|
|
||
| def test_extract_from_vdb_grid(self, grid345): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. parameterize the test to try out all normally supported vdb gridtypes |
||
| data, g = grid345 | ||
| g.metadata["name"] = "new_density" | ||
|
|
||
| native = g.convert_to("vdb") | ||
| new_vdb_grid = Grid(grid=native) | ||
|
|
||
| assert_allclose(new_vdb_grid.grid, g.grid, rtol=1e-5) | ||
| assert_allclose(new_vdb_grid.origin, g.origin, rtol=1e-5) | ||
| assert_allclose(new_vdb_grid.delta, g.delta, rtol=1e-5) | ||
| assert new_vdb_grid.metadata["name"] == "new_density" | ||
| assert new_vdb_grid.grid.dtype == np.dtype("float32") | ||
| assert_allclose(new_vdb_grid.grid, data, rtol=1e-5) | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also include a test that shows that file round-tripping works, e.g.,
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Include a test that reads the test vdb file directly with OpenVDB first. |
||
|
|
||
| @pytest.mark.skipif( | ||
| not HAS_OPENVDB, reason="Need openvdb to test import error handling" | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.