Skip to content

Commit 4c0e63e

Browse files
committed
Creating a Grid from a native object done
1 parent d1159b1 commit 4c0e63e

3 files changed

Lines changed: 151 additions & 14 deletions

File tree

gridData/OpenVDB.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,13 @@ def __init__(
158158
self.metadata = {}
159159

160160
if grid is not None:
161-
self._populate(grid, origin, delta)
162-
self.vdb_grid = self._create_openvdb_grid()
161+
if isinstance(grid, vdb.GridBase):
162+
self.vdb_grid = grid
163+
print("hello")
164+
self._extract_from_vdb_grid()
165+
else:
166+
self._populate(grid, origin, delta)
167+
self.vdb_grid = self._create_openvdb_grid()
163168
else:
164169
self.grid = None
165170
self.origin = None
@@ -322,6 +327,60 @@ def _create_openvdb_grid(self):
322327

323328
return vdb_grid
324329

330+
def _extract_from_vdb_grid(self):
331+
"""Extract numpy array, origin, delta from stored VDB grid.
332+
333+
This method converts the sparse VDB grid to a dense numpy array
334+
and extracts the transform information.
335+
"""
336+
if self.vdb_grid.name:
337+
self.name = self.vdb_grid.name
338+
339+
for key in self.vdb_grid.metadata:
340+
try:
341+
self.metadata[key] = self.vdb_grid[key]
342+
except (TypeError, ValueError):
343+
pass
344+
345+
transformation = self.vdb_grid.transform
346+
347+
v_origin = numpy.array(transformation.indexToWorld([0, 0, 0]))
348+
v_delta = numpy.array(transformation.indexToWorld([1, 1, 1])) - v_origin
349+
350+
self.origin = v_origin
351+
self.delta = v_delta
352+
353+
vdb_native_dtype = {
354+
"BoolGrid": numpy.dtype("bool"),
355+
"Int32Grid": numpy.dtype("int32"),
356+
"Int64Grid": numpy.dtype("int64"),
357+
"FloatGrid": numpy.dtype("float32"),
358+
"DoubleGrid": numpy.dtype("float64"),
359+
"HalfGrid": numpy.dtype("float16"),
360+
"Vec3SGrid": numpy.dtype("float32"),
361+
"Vec3DGrid": numpy.dtype("float64"),
362+
}
363+
dtype = vdb_native_dtype.get(
364+
type(self.vdb_grid).__name__, numpy.dtype("float32")
365+
)
366+
367+
bbox = self.vdb_grid.evalActiveVoxelBoundingBox()
368+
369+
if bbox is None or bbox[0] == bbox[1]:
370+
self.grid = numpy.zeros((0, 0, 0), dtype=dtype)
371+
return
372+
373+
shape = tuple(numpy.array(bbox[1]) - numpy.array(bbox[0]) + 1)
374+
375+
self.grid = numpy.zeros(shape, dtype=dtype)
376+
print(dtype)
377+
self.vdb_grid.copyToArray(self.grid, ijk=bbox[0])
378+
379+
if not numpy.all(numpy.array(bbox[0]) == 0):
380+
self.origin = numpy.array(
381+
transformation.indexToWorld(numpy.array(bbox[0]).tolist())
382+
)
383+
325384
def write(self, filename):
326385
"""Write the field to an OpenVDB file.
327386

gridData/core.py

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import pickle
3939

4040
import numpy
41+
import openvdb as vdb
4142

4243
# For interpolated grids: need scipy.ndimage but we import it only when needed:
4344
# import scipy
@@ -250,7 +251,10 @@ def __init__(
250251
self.interpolation_cval = None # default to using min(grid)
251252

252253
if grid is not None:
253-
if isinstance(grid, str):
254+
if self._is_native_object(grid):
255+
self._load_from_native(grid)
256+
filename = None
257+
elif isinstance(grid, str):
254258
# can probably safely try to load() it...
255259
filename = grid
256260
else:
@@ -273,7 +277,7 @@ def __init__(
273277
file_format=file_format,
274278
assume_volumetric=assume_volumetric,
275279
)
276-
else:
280+
elif not self._is_native_object(grid):
277281
self._load(grid, edges, metadata, origin, delta)
278282

279283
@property
@@ -622,38 +626,76 @@ def _load_plt(self, filename, **kwargs):
622626
grid, edges = g.histogramdd()
623627
self._load(grid=grid, edges=edges, metadata=self.metadata)
624628

629+
def _is_native_object(self, obj):
630+
"""Check if object is a native format object (OpenVDB grid, MRC object).
631+
632+
Parameters
633+
----------
634+
obj : object
635+
Object to check
636+
637+
Returns
638+
-------
639+
bool
640+
True if obj is a native format object
641+
"""
642+
if isinstance(obj, vdb.GridBase):
643+
return True
644+
645+
return False
646+
647+
def _load_from_native(self, obj):
648+
"""Load Grid from a native format object.
649+
650+
Parameters
651+
----------
652+
obj : object
653+
Native format object (openvdb.GridBase, mrcfile.mrcfile.MrcFile, etc.)
654+
655+
"""
656+
self.vdb_field = OpenVDB.OpenVDBField(grid=obj)
657+
658+
origin = self.vdb_field.origin
659+
delta = self.vdb_field.delta
660+
grid = self.vdb_field.grid
661+
662+
self.metadata = self.vdb_field.metadata
663+
self._from_native_vdb = True
664+
665+
self._load(grid=grid, origin=origin, delta=delta)
666+
625667
def convert_to(self, format_specifier, tolerance=None, **kwargs):
626668
"""generates an instance of the native object for a given format
627-
669+
628670
Implemented formats:
629-
671+
630672
vdb
631673
:mod:`OpenVDB`
632-
674+
633675
Parameters
634676
----------
635-
format_specifier : str
677+
format_specifier : str
636678
vdb, etc
637679
638680
Returns
639681
-------
640682
native object
641-
683+
642684
"""
643685
formats = ("mrc", "vdb")
644-
if (format_specifier.lower() == formats[1]):
686+
if format_specifier.lower() == formats[1]:
645687
grid_name = self.metadata.get("name", "density")
646-
vdb_field = OpenVDB.OpenVDBField(
688+
vdb_field = OpenVDB.OpenVDBField(
647689
grid=self.grid,
648690
origin=self.origin,
649691
delta=self.delta,
650692
name=grid_name,
651693
tolerance=tolerance,
652-
metadata=self.metadata
694+
metadata=self.metadata,
653695
)
654-
696+
655697
return vdb_field.vdb_grid
656-
698+
657699
raise ValueError(f"Unsupported convert_to format : {format_specifier}")
658700

659701
def export(
@@ -773,6 +815,14 @@ def _export_vdb(self, filename, tolerance=None, **kwargs):
773815
774816
For the file format see https://www.openvdb.org
775817
"""
818+
if (
819+
hasattr(self, "_from_native_vdb")
820+
and self._from_native_vdb
821+
and tolerance is None
822+
):
823+
self.vdb_field.write(filename)
824+
return
825+
776826
if self.grid.ndim != 3:
777827
raise ValueError(
778828
f"OpenVDB export requires a 3D grid, got {self.grid.ndim}D"

gridData/tests/test_vdb.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import numpy as np
22
from numpy.testing import assert_allclose
3+
import os
34

45
import pytest
56
from unittest.mock import patch
@@ -362,6 +363,33 @@ def test_grid_convert_to_vdb(self, grid345):
362363

363364
assert isinstance(native, vdb.GridBase)
364365

366+
def test_grid_from_native_vdb_preserve_values(self, grid345):
367+
data, g = grid345
368+
native = g.convert_to("vdb")
369+
g2 = Grid(grid=native)
370+
371+
assert g._is_native_object(native) is True
372+
373+
assert g2.grid.shape == data.shape
374+
assert_allclose(g2.origin, g.origin, rtol=1e-5)
375+
assert_allclose(g2.delta, g.delta, rtol=1e-5)
376+
assert_allclose(g2.grid, data, rtol=1e-5)
377+
378+
def test_grid_from_native_vdb_export_with_tolerance(self, tmpdir, grid345):
379+
data, g = grid345
380+
native = g.convert_to("vdb")
381+
g2 = Grid(grid=native)
382+
383+
out_no_tol = str(tmpdir / "no_tolerance.vdb")
384+
out_with_tol = str(tmpdir / "with_tolerance.vdb")
385+
386+
g2.export(out_no_tol)
387+
g2.export(out_with_tol, tolerance=5.0)
388+
389+
size_no_tol = os.path.getsize(out_no_tol)
390+
size_with_tol = os.path.getsize(out_with_tol)
391+
assert size_with_tol < size_no_tol
392+
365393

366394
@pytest.mark.skipif(
367395
not HAS_OPENVDB, reason="Need openvdb to test import error handling"

0 commit comments

Comments
 (0)