From 0e318432ecd951967a776c4680df308ff4628126 Mon Sep 17 00:00:00 2001 From: spyke7 Date: Sat, 30 May 2026 00:43:49 +0530 Subject: [PATCH 1/9] Added support for loading native object for openvdb --- CHANGELOG | 11 ++++ gridData/OpenVDB.py | 102 +++++++++++++++++++++++++++++-------- gridData/core.py | 15 ++++++ gridData/tests/test_vdb.py | 14 +++++ 4 files changed, 120 insertions(+), 22 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0beb1f0..b932862 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,17 @@ The rules for this file: * accompany each entry with github issue/PR number (Issue #xyz) ------------------------------------------------------------------------------- +??/??/???? orbeckst, spyke7 + + * 1.2.1 + + Enhancements + + * Implemented loading of a Grid from a native object for OpenVDB (Issue #162, PR #169) + + Fixes + + 05/22/2026 orbeckst, spyke7 * 1.2.0 diff --git a/gridData/OpenVDB.py b/gridData/OpenVDB.py index 6c0b30a..98a2cee 100644 --- a/gridData/OpenVDB.py +++ b/gridData/OpenVDB.py @@ -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 = { + 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")], + } + 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): + 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") + 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 + + bbox = self.vdb_grid.evalActiveVoxelBoundingBox() + + if bbox is None or bbox[0] == bbox[1]: + self.grid = np.zeros((0, 0, 0), dtype=dtype) + return + + shape = tuple(np.array(bbox[1]) - np.array(bbox[0]) + 1) + + self.grid = np.zeros(shape, dtype=dtype) + print(dtype) + 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") diff --git a/gridData/core.py b/gridData/core.py index 85c1094..fcc90bd 100644 --- a/gridData/core.py +++ b/gridData/core.py @@ -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 if isinstance(grid, str): # can probably safely try to load() it... filename = grid diff --git a/gridData/tests/test_vdb.py b/gridData/tests/test_vdb.py index c29be27..6e851a6 100644 --- a/gridData/tests/test_vdb.py +++ b/gridData/tests/test_vdb.py @@ -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): + 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) + @pytest.mark.skipif( not HAS_OPENVDB, reason="Need openvdb to test import error handling" From eb448e3b1228134f00f0b98994b55e305803b963 Mon Sep 17 00:00:00 2001 From: spyke7 Date: Sat, 30 May 2026 00:49:38 +0530 Subject: [PATCH 2/9] Updated CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b932862..947b6ab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,7 +19,7 @@ The rules for this file: Enhancements - * Implemented loading of a Grid from a native object for OpenVDB (Issue #162, PR #169) + * Implemented loading of a Grid from a native object for OpenVDB (Issue #162, PR #170) Fixes From d660aef47c90eb293e3592ced539a73d4d1d540f Mon Sep 17 00:00:00 2001 From: Shreejan Dolai Date: Sat, 30 May 2026 11:23:14 +0530 Subject: [PATCH 3/9] Update CHANGELOG Co-authored-by: Oliver Beckstein --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 947b6ab..56a995d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,7 +15,7 @@ The rules for this file: ------------------------------------------------------------------------------- ??/??/???? orbeckst, spyke7 - * 1.2.1 + * 1.3.0 Enhancements From faf508b9f978be59033a849073059d19daf2bd26 Mon Sep 17 00:00:00 2001 From: Shreejan Dolai Date: Sat, 30 May 2026 13:30:05 +0530 Subject: [PATCH 4/9] Update CHANGELOG Co-authored-by: Oliver Beckstein --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 56a995d..f733214 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,7 +13,7 @@ The rules for this file: * accompany each entry with github issue/PR number (Issue #xyz) ------------------------------------------------------------------------------- -??/??/???? orbeckst, spyke7 +??/??/???? spyke7 * 1.3.0 From 5d0dfac4f0b2dae9522c055196f0ee3e1343a25c Mon Sep 17 00:00:00 2001 From: spyke7 Date: Sat, 30 May 2026 23:28:20 +0530 Subject: [PATCH 5/9] Updated test_vdb and OpenVDB.py --- gridData/OpenVDB.py | 47 +++++++++++++++++----------- gridData/tests/test_vdb.py | 63 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 18 deletions(-) diff --git a/gridData/OpenVDB.py b/gridData/OpenVDB.py index 98a2cee..c54cedf 100644 --- a/gridData/OpenVDB.py +++ b/gridData/OpenVDB.py @@ -155,8 +155,8 @@ class OpenVDBField(object): g.export('output.vdb', format='vdb') """ - # dtype maps - _DATATYPES = { + # dtype maps for numpy to vdb + _DTYPES_NP2VDB = { np.dtype("bool"): ["BoolGrid"], np.dtype("int8"): ["Int32Grid", "FloatGrid"], np.dtype("uint8"): ["Int32Grid", "FloatGrid"], @@ -171,6 +171,16 @@ class OpenVDBField(object): np.dtype("float64"): ["DoubleGrid", DownCastTo("FloatGrid")], } + # dtype maps for vdb to numpy + _DTYPES_VDB2NP = { + "BoolGrid": np.dtype("bool"), + "Int32Grid": np.dtype("int32"), + "Int64Grid": np.dtype("int64"), + "FloatGrid": np.dtype("float32"), + "DoubleGrid": np.dtype("float64"), + "HalfGrid": np.dtype("float16"), + } + def __init__( self, grid=None, @@ -303,8 +313,11 @@ def _extract_from_vdb_grid(self): for key in self.vdb_grid.metadata: try: self.metadata[key] = self.vdb_grid[key] - except (TypeError, ValueError): - pass + except (TypeError, ValueError) as e: + warnings.warn( + f"Could not read metadata key '{key}' from VDB grid: {e}", + UserWarning, + ) transformation = self.vdb_grid.transform @@ -314,23 +327,21 @@ def _extract_from_vdb_grid(self): self.origin = v_origin self.delta = v_delta - dtype = np.dtype("float32") - 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 + dtype = self._DTYPES_VDB2NP.get(type(self.vdb_grid).__name__) + if dtype is None: + warnings.warn( + f"Unknown VDB grid type '{type(self.vdb_grid).__name__}', defaulting to float32.", + RuntimeWarning, ) - - if vdb_class_name == canonical_name: - dtype = numpy_dtype - break + dtype = np.dtype("float32") bbox = self.vdb_grid.evalActiveVoxelBoundingBox() - if bbox is None or bbox[0] == bbox[1]: + if bbox is None or any(bbox[1][i] < bbox[0][i] for i in range(3)): + warnings.warn( + "VDB grid has no active voxels (empty bounding box). Returning an empty array of shape (0, 0, 0).", + RuntimeWarning, + ) self.grid = np.zeros((0, 0, 0), dtype=dtype) return @@ -405,7 +416,7 @@ def _get_best_grid_type(self): If dtype is not supported or no suitable grid type is available """ try: - vdb_gridtypes = self._DATATYPES[self.grid.dtype] + vdb_gridtypes = self._DTYPES_NP2VDB[self.grid.dtype] except KeyError: raise TypeError(f"Data type {self.grid.dtype} not supported for VDB") diff --git a/gridData/tests/test_vdb.py b/gridData/tests/test_vdb.py index 6e851a6..41b6692 100644 --- a/gridData/tests/test_vdb.py +++ b/gridData/tests/test_vdb.py @@ -392,6 +392,25 @@ 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_file_roundtrip_native_vdbgrid(self, tmpdir, grid345): + data, g = grid345 + g.metadata["name"] = "new_density" + + outfile = str(tmpdir / "roundtrip.vdb") + g.export(outfile) + + grids, _ = vdb.readAll(outfile) + assert len(grids) == 1 + + grid_vdb = grids[0] + new_vdb_grid = Grid(grid=grid_vdb) + 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) + def test_extract_from_vdb_grid(self, grid345): data, g = grid345 g.metadata["name"] = "new_density" @@ -406,6 +425,50 @@ def test_extract_from_vdb_grid(self, grid345): assert new_vdb_grid.grid.dtype == np.dtype("float32") assert_allclose(new_vdb_grid.grid, data, rtol=1e-5) + @pytest.mark.parametrize( + "vdb_type,np_dtype", list(gridData.OpenVDB.OpenVDBField._DTYPES_VDB2NP.items()) + ) + def test_extract_dtype_roundtrip(self, vdb_type, np_dtype): + vdb_cls = getattr(vdb, vdb_type, None) + if vdb_cls is None: + pytest.skip(f"{vdb_type} not available in this openvdb build") + + native = vdb_cls() + native.name = "test" + acc = native.getAccessor() + acc.setValueOn((0, 0, 0), True if np_dtype == np.dtype("bool") else 1) + acc.setValueOn((1, 1, 1), True if np_dtype == np.dtype("bool") else 2) + + field = gridData.OpenVDB.OpenVDBField(grid=native) + + assert field.grid.dtype == np_dtype + assert field.grid.shape != (0, 0, 0) + + def test_extract_unknown_vdb_type_warns(self): + native = vdb.FloatGrid() + acc = native.getAccessor() + acc.setValueOn((0, 0, 0), 1.0) + + patched = { + k: v + for k, v in gridData.OpenVDB.OpenVDBField._DTYPES_VDB2NP.items() + if k != "FloatGrid" + } + with patch.object(gridData.OpenVDB.OpenVDBField, "_DTYPES_VDB2NP", patched): + with pytest.warns(RuntimeWarning, match="Unknown VDB grid type"): + field = gridData.OpenVDB.OpenVDBField(grid=native) + + assert field.grid.dtype == np.dtype("float32") + + def test_extract_empty_vdb_grid_warns(self): + native = vdb.FloatGrid() + native.clear() + + with pytest.warns(RuntimeWarning, match="no active voxels"): + field = gridData.OpenVDB.OpenVDBField(grid=native) + + assert field.grid.shape == (0, 0, 0) + @pytest.mark.skipif( not HAS_OPENVDB, reason="Need openvdb to test import error handling" From 099ea1a43dfc00df56cd7327154eef764277f5d1 Mon Sep 17 00:00:00 2001 From: spyke7 Date: Sun, 31 May 2026 00:07:10 +0530 Subject: [PATCH 6/9] Updated test_vdb.py --- gridData/tests/test_vdb.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/gridData/tests/test_vdb.py b/gridData/tests/test_vdb.py index 41b6692..bfff576 100644 --- a/gridData/tests/test_vdb.py +++ b/gridData/tests/test_vdb.py @@ -469,6 +469,29 @@ def test_extract_empty_vdb_grid_warns(self): assert field.grid.shape == (0, 0, 0) + def test_extract_unreadable_metadata_warns(self, grid345): + data, g = grid345 + g.metadata["readable"] = "hello" + g.metadata["factor"] = 42 + + native = g.convert_to("vdb") + + original_getitem = native.__class__.__getitem__ + + def patched_getitem(self, key): + if key == "factor": + raise TypeError("unsupported metadata type") + return original_getitem(self, key) + + with patch.object(native.__class__, "__getitem__", patched_getitem): + with pytest.warns( + UserWarning, match="Could not read metadata key 'factor'" + ): + field = gridData.OpenVDB.OpenVDBField(grid=native) + + assert field.metadata.get("readable") == "hello" + assert "factor" not in field.metadata + @pytest.mark.skipif( not HAS_OPENVDB, reason="Need openvdb to test import error handling" From d1c8d41ec2f6d1133af076b3c25bedc994f23d02 Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Wed, 8 Jul 2026 14:05:45 -0700 Subject: [PATCH 7/9] fixed initialization of DXClasses - updated init signature of OpenDX.DXClass to include name and components - consistently use super() in DXClass-child classes - use zero-arg super() calls --- CHANGELOG | 10 ++++++++++ gridData/OpenDX.py | 40 +++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0beb1f0..45ce4fb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,16 @@ The rules for this file: * accompany each entry with github issue/PR number (Issue #xyz) ------------------------------------------------------------------------------- +MM/DD/2026 orbeckst + + * 1.3.0 + + Changes + + * update init signature for OpenDX.DXClass to include name and components + as optional kwargs to streamline init in child classes (PR #179) + + 05/22/2026 orbeckst, spyke7 * 1.2.0 diff --git a/gridData/OpenDX.py b/gridData/OpenDX.py index 5cf2617..a6e4d63 100644 --- a/gridData/OpenDX.py +++ b/gridData/OpenDX.py @@ -229,12 +229,12 @@ def _gzip_open(filename, mode="rt"): class DXclass(object): """'class' object as defined by OpenDX""" - def __init__(self, classid): + def __init__(self, classid, name=None, component=None): """id is the object number""" self.id = classid # serial number of the object - self.name = None # name of the DXclass - self.component = None # component type - self.D = None # dimensions + self.name = name # name of the DXclass + self.component = component # component type + self.D = 3 # dimensions def write(self, stream, optstring="", quote=False): """write the 'object' line; additional args are packed in string""" @@ -279,12 +279,13 @@ class gridpositions(DXclass): def __init__(self, classid, shape=None, origin=None, delta=None, **kwargs): if shape is None or origin is None or delta is None: raise ValueError("all keyword arguments are required") - self.id = classid - self.name = "gridpositions" - self.component = "positions" + super().__init__(classid, + name="gridpositions", + component="positions") self.shape = numpy.asarray(shape) # D dimensional shape self.origin = numpy.asarray(origin) # D vector self.rank = len(self.shape) # D === rank + assert self.rank == self.D, "DXClass is only used for 3D arrays" self.delta = numpy.asarray(delta) # DxD array of grid spacings # gridDataFormats actually provides a simple 1D array with the deltas because only @@ -301,7 +302,7 @@ def __init__(self, classid, shape=None, origin=None, delta=None, **kwargs): ) def write(self, stream): - super(gridpositions, self).write( + super().write( stream, ("counts " + self.ndformat(" %d")) % tuple(self.shape) ) self._write_line(stream, "origin %f %f %f\n" % tuple(self.origin)) @@ -329,9 +330,9 @@ class gridconnections(DXclass): def __init__(self, classid, shape=None, **kwargs): if shape is None: raise ValueError("all keyword arguments are required") - self.id = classid - self.name = "gridconnections" - self.component = "connections" + super().__init__(classid, + name="gridconnections", + component="connections") self.shape = numpy.asarray(shape) # D dimensional shape def write(self, stream): @@ -410,9 +411,9 @@ def __init__(self, classid, array=None, type=None, typequote='"', **kwargs): """ if array is None: raise ValueError("array keyword argument is required") - self.id = classid - self.name = "array" - self.component = "data" + super().__init__(classid, + name="array", + component="data") # detect type https://github.com/MDAnalysis/GridDataFormats/issues/35 if type is None: self.array = numpy.asarray(array) @@ -464,7 +465,7 @@ def write(self, stream): ).format(self.type, list(self.dx_types.keys())) ) typelabel = self.typequote + self.type + self.typequote - super(array, self).write( + super().write( stream, "type {0} rank 0 items {1} data follows".format(typelabel, self.array.size), ) @@ -544,6 +545,10 @@ def __init__(self, classid="0", components=None, comments=None): dx = OpenDX.field('density',[gridpoints,gridconnections,array]) """ + super().__init__(classid, # can be an arbitrary string + name="field", + component=None, # cannot be a component of a field + ) if components is None: components = dict(positions=None, connections=None, data=None) if comments is None: @@ -553,9 +558,6 @@ def __init__(self, classid="0", components=None, comments=None): ] elif type(comments) is not list: comments = [str(comments)] - self.id = classid # can be an arbitrary string - self.name = "field" - self.component = None # cannot be a component of a field self.components = components self.comments = comments @@ -647,7 +649,7 @@ def write(self, filename): for component, object in self.sorted_components(): object.write(outfile) # the field object itself - super(field, self).write(outfile, quote=True) + super().write(outfile, quote=True) for component, object in self.sorted_components(): self._write_line( outfile, 'component "%s" value %s\n' % (component, str(object.id)) From 824b4af3e807098497c857d64870132e738e4529 Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Wed, 8 Jul 2026 14:11:17 -0700 Subject: [PATCH 8/9] reformatted OpenDX.py with black --- gridData/OpenDX.py | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/gridData/OpenDX.py b/gridData/OpenDX.py index a6e4d63..3e5e9bc 100644 --- a/gridData/OpenDX.py +++ b/gridData/OpenDX.py @@ -202,6 +202,7 @@ :members: """ + import numpy import re import gzip @@ -279,9 +280,7 @@ class gridpositions(DXclass): def __init__(self, classid, shape=None, origin=None, delta=None, **kwargs): if shape is None or origin is None or delta is None: raise ValueError("all keyword arguments are required") - super().__init__(classid, - name="gridpositions", - component="positions") + super().__init__(classid, name="gridpositions", component="positions") self.shape = numpy.asarray(shape) # D dimensional shape self.origin = numpy.asarray(origin) # D vector self.rank = len(self.shape) # D === rank @@ -302,9 +301,7 @@ def __init__(self, classid, shape=None, origin=None, delta=None, **kwargs): ) def write(self, stream): - super().write( - stream, ("counts " + self.ndformat(" %d")) % tuple(self.shape) - ) + super().write(stream, ("counts " + self.ndformat(" %d")) % tuple(self.shape)) self._write_line(stream, "origin %f %f %f\n" % tuple(self.origin)) for delta in self.delta: self._write_line( @@ -330,9 +327,7 @@ class gridconnections(DXclass): def __init__(self, classid, shape=None, **kwargs): if shape is None: raise ValueError("all keyword arguments are required") - super().__init__(classid, - name="gridconnections", - component="connections") + super().__init__(classid, name="gridconnections", component="connections") self.shape = numpy.asarray(shape) # D dimensional shape def write(self, stream): @@ -411,9 +406,7 @@ def __init__(self, classid, array=None, type=None, typequote='"', **kwargs): """ if array is None: raise ValueError("array keyword argument is required") - super().__init__(classid, - name="array", - component="data") + super().__init__(classid, name="array", component="data") # detect type https://github.com/MDAnalysis/GridDataFormats/issues/35 if type is None: self.array = numpy.asarray(array) @@ -545,10 +538,11 @@ def __init__(self, classid="0", components=None, comments=None): dx = OpenDX.field('density',[gridpoints,gridconnections,array]) """ - super().__init__(classid, # can be an arbitrary string - name="field", - component=None, # cannot be a component of a field - ) + super().__init__( + classid, # can be an arbitrary string + name="field", + component=None, # cannot be a component of a field + ) if components is None: components = dict(positions=None, connections=None, data=None) if comments is None: @@ -586,8 +580,8 @@ def from_grid(cls, grid, type=None, typequote='"', **kwargs): ------- field OpenDX field wrapper - - + + .. versionadded:: 1.2.0 """ comments = [ @@ -614,10 +608,10 @@ def from_grid(cls, grid, type=None, typequote='"', **kwargs): @property def native(self): """Return native object - + The "native" object is the :class:`gridData.OpenDX.field` itself. - - + + .. versionadded:: 1.2.0 """ return self @@ -685,9 +679,11 @@ def read(self, stream): except (UnicodeDecodeError, RecursionError) as err: # parser got confused, likely not a valid file # (RecursionError was only observed on Windows) - raise ValueError("DX file could not be read. " - "The original error was\n" - f" {err.__class__.__name__}: {err}") + raise ValueError( + "DX file could not be read. " + "The original error was\n" + f" {err.__class__.__name__}: {err}" + ) def add(self, component, DXobj): """add a component to the field""" From 86b6acf0e6e673c81a1d2d807319bb93ebe8844e Mon Sep 17 00:00:00 2001 From: spyke7 Date: Thu, 9 Jul 2026 19:14:52 +0530 Subject: [PATCH 9/9] Updated openvdb and test_vdb for a type error warning of different grid type --- gridData/OpenVDB.py | 11 +++++------ gridData/tests/test_vdb.py | 4 +--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/gridData/OpenVDB.py b/gridData/OpenVDB.py index c54cedf..507c0c9 100644 --- a/gridData/OpenVDB.py +++ b/gridData/OpenVDB.py @@ -327,13 +327,12 @@ def _extract_from_vdb_grid(self): self.origin = v_origin self.delta = v_delta - dtype = self._DTYPES_VDB2NP.get(type(self.vdb_grid).__name__) - if dtype is None: - warnings.warn( - f"Unknown VDB grid type '{type(self.vdb_grid).__name__}', defaulting to float32.", - RuntimeWarning, + try: + dtype = self._DTYPES_VDB2NP[type(self.vdb_grid).__name__] + except KeyError: + raise TypeError( + f"Unknown VDB grid type '{type(self.vdb_grid).__name__}', cannot map to a numpy dtype." ) - dtype = np.dtype("float32") bbox = self.vdb_grid.evalActiveVoxelBoundingBox() diff --git a/gridData/tests/test_vdb.py b/gridData/tests/test_vdb.py index bfff576..a9df056 100644 --- a/gridData/tests/test_vdb.py +++ b/gridData/tests/test_vdb.py @@ -455,11 +455,9 @@ def test_extract_unknown_vdb_type_warns(self): if k != "FloatGrid" } with patch.object(gridData.OpenVDB.OpenVDBField, "_DTYPES_VDB2NP", patched): - with pytest.warns(RuntimeWarning, match="Unknown VDB grid type"): + with pytest.raises(TypeError, match="Unknown VDB grid type"): field = gridData.OpenVDB.OpenVDBField(grid=native) - assert field.grid.dtype == np.dtype("float32") - def test_extract_empty_vdb_grid_warns(self): native = vdb.FloatGrid() native.clear()