Skip to content

Commit cb14c1d

Browse files
committed
Merge branch '414p1-change-to-vtkhdf' into 'development'
initial support for VTKHDF See merge request damask/DAMASK!1080
2 parents f43a9c7 + fbb2d12 commit cb14c1d

File tree

3 files changed

+97
-35
lines changed

3 files changed

+97
-35
lines changed

.gitlab-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ workflow:
4747

4848
.python_default:
4949
extends: .python_base
50-
variables: {PYTHON_IMAGE: 2025.02.25}
50+
variables: {PYTHON_IMAGE: 2025.06.05}
5151

5252
.python_earliest:
5353
extends: .python_base

python/damask/_vtk.py

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313
except ImportError:
1414
pass
1515

16-
from vtkmodules.vtkIOXML import vtkXMLReader, vtkXMLWriter
17-
1816
from vtkmodules.vtkCommonCore import (
17+
vtkVersion,
1918
vtkPoints,
2019
vtkStringArray,
2120
vtkLookupTable,
2221
)
22+
23+
if has_vtkhdf := (np.lib.NumpyVersion(vtkVersion.GetVTKVersion()) >= '9.4.0'):
24+
from vtkmodules.vtkIOHDF import vtkHDFReader, vtkHDFWriter
25+
2326
from vtkmodules.vtkCommonDataModel import (
2427
vtkDataSet,
2528
vtkCellArray,
@@ -33,6 +36,7 @@
3336
vtkDataSetWriter,
3437
)
3538
from vtkmodules.vtkIOXML import (
39+
vtkXMLWriter,
3640
vtkXMLImageDataReader,
3741
vtkXMLImageDataWriter,
3842
vtkXMLRectilinearGridReader,
@@ -135,6 +139,7 @@ def __eq__(self,
135139

136140

137141
def copy(self):
142+
"""Return a deep copy."""
138143
if isinstance(self.vtk_data,vtkImageData):
139144
dup = vtkImageData()
140145
elif isinstance(self.vtk_data,vtkUnstructuredGrid):
@@ -143,8 +148,6 @@ def copy(self):
143148
dup = vtkPolyData()
144149
elif isinstance(self.vtk_data,vtkRectilinearGrid):
145150
dup = vtkRectilinearGrid()
146-
else:
147-
raise TypeError
148151

149152
dup.DeepCopy(self.vtk_data)
150153

@@ -153,7 +156,7 @@ def copy(self):
153156

154157
@property
155158
def comments(self) -> list[str]:
156-
"""Return the comments."""
159+
"""Comments in vtkdata""" # noqa: D415
157160
field_data = self.vtk_data.GetFieldData()
158161
for a in range(field_data.GetNumberOfArrays()):
159162
if field_data.GetArrayName(a) == 'comments':
@@ -170,7 +173,7 @@ def comments(self,
170173
Parameters
171174
----------
172175
comments : sequence of str
173-
Comments.
176+
Comments to assign to vtkdata.
174177
"""
175178
s = vtkStringArray()
176179
s.SetName('comments')
@@ -181,19 +184,19 @@ def comments(self,
181184

182185
@property
183186
def N_points(self) -> int:
184-
"""Number of points in vtkdata."""
187+
"""Number of points in vtkdata""" # noqa: D415
185188
return self.vtk_data.GetNumberOfPoints()
186189

187190

188191
@property
189192
def N_cells(self) -> int:
190-
"""Number of cells in vtkdata."""
193+
"""Number of cells in vtkdata""" # noqa: D415
191194
return self.vtk_data.GetNumberOfCells()
192195

193196

194197
@property
195198
def labels(self):
196-
"""Labels of datasets."""
199+
"""Labels of datasets""" # noqa: D415
197200
labels = {}
198201

199202
cell_data = self.vtk_data.GetCellData()
@@ -348,46 +351,55 @@ def load(fname: Union[str, Path],
348351
----------
349352
fname : str or pathlib.Path
350353
Filename to read.
351-
Valid extensions are .vti, .vtu, .vtp, .vtr, and .vtk.
354+
Valid extensions are .vti, .vtu, .vtp, .vtr, vtkhdf, and .vtk.
352355
dataset_type : {'ImageData', 'UnstructuredGrid', 'PolyData', 'RectilinearGrid'}, optional
353356
Name of the vtkDataSet subclass when opening a .vtk file.
354357
355358
Returns
356359
-------
357360
loaded : damask.VTK
358361
VTK-based geometry from file.
362+
363+
Notes
364+
-----
365+
Loading VTKHDF files requires VTK 9.4 or later and presently supports
366+
PolyData, UnstructuredGrid, and ImageData. Loading ImageData is untested
367+
because VTK does not yet provide the functionality to write ImageData
368+
into VTKHDF format.
359369
"""
360370
if not Path(fname).expanduser().is_file(): # vtk has a strange error handling
361371
raise FileNotFoundError(f'file "{fname}" not found')
372+
362373
if (ext := Path(fname).suffix) == '.vtk' or dataset_type is not None:
363-
vtk_reader = vtkGenericDataObjectReader()
364-
vtk_reader.SetFileName(str(Path(fname).expanduser()))
374+
reader_legacy = vtkGenericDataObjectReader()
375+
reader_legacy.SetFileName(str(Path(fname).expanduser()))
376+
reader_legacy.Update()
365377
if dataset_type is None:
366-
raise TypeError('dataset type for *.vtk file not given')
367-
vtk_reader.Update()
368-
if dataset_type.lower().endswith(('imagedata', 'image_data')):
369-
vtk_data = vtk_reader.GetStructuredPointsOutput()
370-
elif dataset_type.lower().endswith(('unstructuredgrid', 'unstructured_grid')):
371-
vtk_data = vtk_reader.GetUnstructuredGridOutput()
372-
elif dataset_type.lower().endswith(('polydata', 'poly_data')):
373-
vtk_data = vtk_reader.GetPolyDataOutput()
374-
elif dataset_type.lower().endswith(('rectilineargrid', 'rectilinear_grid')):
375-
vtk_data = vtk_reader.GetRectilinearGridOutput()
376-
else:
377-
raise TypeError(f'unknown dataset type "{dataset_type}" for vtk file')
378+
raise TypeError('missing dataset type for legacy VTK file')
379+
dtl = dataset_type.lower()
380+
vtk_data = (
381+
reader_legacy.GetStructuredPointsOutput() if dtl.endswith(('imagedata', 'image_data')) else
382+
reader_legacy.GetUnstructuredGridOutput() if dtl.endswith(('unstructuredgrid', 'unstructured_grid')) else
383+
reader_legacy.GetPolyDataOutput() if dtl.endswith(('polydata', 'poly_data')) else
384+
reader_legacy.GetRectilinearGridOutput() if dtl.endswith(('rectilineargrid', 'rectilinear_grid')) else
385+
None
386+
)
387+
if vtk_data is None:
388+
raise TypeError(f'unsupported VTK dataset type "{dataset_type}"')
378389
else:
379-
xml_reader: Optional[vtkXMLReader] = (
390+
reader = (
380391
vtkXMLImageDataReader() if ext == '.vti' else
381392
vtkXMLUnstructuredGridReader() if ext == '.vtu' else
382393
vtkXMLPolyDataReader() if ext == '.vtp' else
383394
vtkXMLRectilinearGridReader() if ext == '.vtr' else
395+
vtkHDFReader() if (ext == '.vtkhdf' and has_vtkhdf) else
384396
None
385397
)
386-
if xml_reader is None:
387-
raise TypeError(f'unknown file extension "{ext}"')
388-
xml_reader.SetFileName(str(Path(fname).expanduser()))
389-
xml_reader.Update()
390-
vtk_data = xml_reader.GetOutputAsDataSet()
398+
if reader is None:
399+
raise TypeError(f'unsupported VTK file extension "{ext}"')
400+
reader.SetFileName(str(Path(fname).expanduser()))
401+
reader.Update()
402+
vtk_data = reader.GetOutputAsDataSet()
391403

392404
return VTK(vtk_data)
393405

@@ -431,8 +443,7 @@ def save(self,
431443
vtkXMLRectilinearGridWriter() if isinstance(self.vtk_data, vtkRectilinearGrid) else
432444
None
433445
)
434-
if writer is None:
435-
raise TypeError(f'unknown vtk_data type "{type(self.vtk_data)}"')
446+
assert writer is not None
436447

437448
default_ext = '.'+writer.GetDefaultFileExtension()
438449
ext = Path(fname).suffix
@@ -456,6 +467,33 @@ def save(self,
456467
writer.Write()
457468

458469

470+
def save_VTKHDF(self,
471+
fname: Union[str, Path]):
472+
"""
473+
Save as VTKHDF file.
474+
475+
Parameters
476+
----------
477+
fname : str or pathlib.Path
478+
Filename to write.
479+
480+
Notes
481+
-----
482+
Saving as VTKHDF file requires VTK 9.4 or later and only supports
483+
PolyData and UnstructuredGrid.
484+
"""
485+
if not has_vtkhdf:
486+
raise NotImplementedError('save as VTKHDF requires VTK 9.4 or later')
487+
if not isinstance(self.vtk_data, (vtkPolyData, vtkUnstructuredGrid)):
488+
raise TypeError(f'unsupported vtk_data type "{type(self.vtk_data)}"')
489+
490+
writer = vtkHDFWriter()
491+
ext = Path(fname).suffix
492+
writer.SetFileName(str(Path(fname).expanduser())+('.vtkhdf' if '.vtkhdf' != ext else ''))
493+
writer.SetInputData(self.vtk_data)
494+
writer.Write()
495+
496+
459497
def set(self,
460498
label: Optional[str] = None,
461499
data: Union[None, np.ndarray, np.ma.MaskedArray] = None,

python/tests/test_VTK.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ def test_polyData(self,np_rng,tmp_path):
7575
vtk = VTK.load(tmp_path/'polyData.vtk','polyData')
7676
assert (string == vtp.as_ASCII() == vtk.as_ASCII())
7777

78+
@pytest.mark.xfail(np.lib.NumpyVersion(vtkVersion.GetVTKVersion()) < '9.4.0',
79+
reason = 'not available in VTK < 9.4')
80+
def test_polyData_VTKHDF(self,np_rng,tmp_path):
81+
points = np_rng.random((100,3))
82+
v = VTK.from_poly_data(points)
83+
v.save_VTKHDF(tmp_path/'polyData')
84+
vtkhdf = VTK.load(tmp_path/'polyData.vtkhdf')
85+
assert (v.as_ASCII() == vtkhdf.as_ASCII())
86+
7887
@pytest.mark.parametrize('cell_type,n',[
7988
('VTK_hexahedron',8),
8089
('TETRA',4),
@@ -95,6 +104,23 @@ def test_unstructuredGrid(self,np_rng,tmp_path,cell_type,n):
95104
vtk = VTK.load(tmp_path/'unstructuredGrid.vtk','unstructuredgrid')
96105
assert (string == vtu.as_ASCII() == vtk.as_ASCII())
97106

107+
@pytest.mark.xfail(np.lib.NumpyVersion(vtkVersion.GetVTKVersion()) < '9.4.0',
108+
reason = 'not available in VTK < 9.4')
109+
@pytest.mark.parametrize('cell_type,n',[
110+
('VTK_hexahedron',8),
111+
('TETRA',4),
112+
('quad',4),
113+
('VTK_TRIANGLE',3)
114+
]
115+
)
116+
def test_unstructuredGrid_VTKHDF(self,np_rng,tmp_path,cell_type,n):
117+
nodes = np_rng.random((n,3))
118+
connectivity = np_rng.choice(np.arange(n),n,False).reshape(-1,n)
119+
v = VTK.from_unstructured_grid(nodes,connectivity,cell_type)
120+
v.save_VTKHDF(tmp_path/'unstructuredGrid')
121+
vtkhdf = VTK.load(tmp_path/'unstructuredGrid.vtkhdf')
122+
assert (v.as_ASCII() == vtkhdf.as_ASCII())
123+
98124

99125
def test_parallel_out(self,np_rng,tmp_path):
100126
points = np_rng.random((102,3))
@@ -228,7 +254,6 @@ def test_comments(self,tmp_path,default):
228254
new = VTK.load(tmp_path/'with_comments.vti')
229255
assert new.comments == ['this is a comment']
230256

231-
@pytest.mark.xfail(vtkVersion.GetVTKMajorVersion()<8, reason='missing METADATA')
232257
def test_compare_reference_polyData(self,update,res_path,tmp_path):
233258
points=np.dstack((np.linspace(0.,1.,10),np.linspace(0.,2.,10),np.linspace(-1.,1.,10))).squeeze()
234259
polyData = VTK.from_poly_data(points).set('coordinates',points)
@@ -239,7 +264,6 @@ def test_compare_reference_polyData(self,update,res_path,tmp_path):
239264
assert polyData.as_ASCII() == reference.as_ASCII() and \
240265
np.allclose(polyData.get('coordinates'),points)
241266

242-
@pytest.mark.xfail(vtkVersion.GetVTKMajorVersion()<8, reason='missing METADATA')
243267
def test_compare_reference_rectilinearGrid(self,update,res_path,tmp_path):
244268
grid = [np.arange(4)**2.,
245269
np.arange(5)**2.,

0 commit comments

Comments
 (0)