VTKio 0.1.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vtkio/__init__.py +27 -0
- vtkio/_git.py +45 -0
- vtkio/helpers.py +110 -0
- vtkio/reader/__init__.py +15 -0
- vtkio/reader/hdf5.py +379 -0
- vtkio/reader/xml.py +712 -0
- vtkio/simplified.py +621 -0
- vtkio/utilities.py +222 -0
- vtkio/version.py +78 -0
- vtkio/vtk_cell_types.py +98 -0
- vtkio/vtk_structures.py +306 -0
- vtkio/writer/__init__.py +16 -0
- vtkio/writer/pvd_writer.py +132 -0
- vtkio/writer/vtkhdf.py +1184 -0
- vtkio/writer/writers.py +393 -0
- vtkio/writer/xml_writer.py +1597 -0
- vtkio-0.1.0.dev2.dist-info/METADATA +86 -0
- vtkio-0.1.0.dev2.dist-info/RECORD +20 -0
- vtkio-0.1.0.dev2.dist-info/WHEEL +4 -0
- vtkio-0.1.0.dev2.dist-info/licenses/LICENSE +28 -0
vtkio/reader/xml.py
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Module for reading data files in VTK's XML based format. This module contains the following classes and functions:
|
|
4
|
+
|
|
5
|
+
Classes
|
|
6
|
+
-------
|
|
7
|
+
Reader()
|
|
8
|
+
Class to read VTK XML data files.
|
|
9
|
+
|
|
10
|
+
Functions
|
|
11
|
+
---------
|
|
12
|
+
convert_to_vtkhdf(x=1)
|
|
13
|
+
Convert a VTK XML file to an equivalent VTKHDF file.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__author__ = 'J.P. Morrissey'
|
|
18
|
+
__copyright__ = 'Copyright 2022-2025'
|
|
19
|
+
__maintainer__ = 'J.P. Morrissey'
|
|
20
|
+
__email__ = 'morrissey.jp@gmail.com'
|
|
21
|
+
__status__ = 'Development'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
import struct
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
import pybase64
|
|
29
|
+
import xmltodict
|
|
30
|
+
from dotwiz import DotWiz
|
|
31
|
+
|
|
32
|
+
from ..helpers import BIG_ENDIAN, LITTLE_ENDIAN, _determine_points_key, _parse_bytecount_type
|
|
33
|
+
from ..utilities import dict_extract_generator, get_recursively
|
|
34
|
+
|
|
35
|
+
# import vtk
|
|
36
|
+
# from vtk.util.numpy_support import vtk_to_numpy
|
|
37
|
+
from ..vtk_structures import (
|
|
38
|
+
Cell,
|
|
39
|
+
Grid,
|
|
40
|
+
GridCoordinates,
|
|
41
|
+
ImageData,
|
|
42
|
+
PolyData,
|
|
43
|
+
PolyDataTopology,
|
|
44
|
+
RectilinearData,
|
|
45
|
+
StructuredData,
|
|
46
|
+
UnstructuredGrid,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
__all__ = ['convert_to_vtkhdf', 'read_vtkxml_data', 'Reader']
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# read using vtk library
|
|
53
|
+
# def read_vtp(path):
|
|
54
|
+
# reader = vtk.vtkXMLPolyDataReader()
|
|
55
|
+
# reader.SetFileName(path)
|
|
56
|
+
# reader.Update()
|
|
57
|
+
# data = reader.GetOutput().GetPointData()
|
|
58
|
+
# field_count = data.GetNumberOfArrays()
|
|
59
|
+
#
|
|
60
|
+
# return {data.GetArrayName(i): vtk_to_numpy(data.GetArray(i)) for i in range(field_count)}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Reader:
|
|
64
|
+
"""VTK XML Reader Class."""
|
|
65
|
+
|
|
66
|
+
supported_filetypes = ['.vti', '.vtr', '.vts', '.vtu', '.vtp']
|
|
67
|
+
|
|
68
|
+
def __init__(self, file_path, encoding='utf-8'):
|
|
69
|
+
"""
|
|
70
|
+
Create an instance of the VTK XML reader.
|
|
71
|
+
|
|
72
|
+
This reads the XML file and extracts the key metadata required to parse the `DataArray`s.
|
|
73
|
+
|
|
74
|
+
Extract the data by calling the `parse()` method.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
file_path: str
|
|
79
|
+
Relative or absolute path of the VTK XML file.
|
|
80
|
+
encoding: str
|
|
81
|
+
Filetype encoding. Default is 'utf-8'.
|
|
82
|
+
"""
|
|
83
|
+
self.file_path = Path(file_path)
|
|
84
|
+
if self.file_path.suffix.lower() not in Reader.supported_filetypes:
|
|
85
|
+
raise ValueError('Unsupported file type')
|
|
86
|
+
|
|
87
|
+
# check if file exists
|
|
88
|
+
if not self.file_path.exists():
|
|
89
|
+
raise FileNotFoundError(f"File {self.file_path} does not exist.")
|
|
90
|
+
|
|
91
|
+
# set placeholders
|
|
92
|
+
self.encoding = encoding
|
|
93
|
+
self.grid = None
|
|
94
|
+
self.topology = None
|
|
95
|
+
self.points = None
|
|
96
|
+
self.field_data = None
|
|
97
|
+
self.cell_data = None
|
|
98
|
+
self.point_data = None
|
|
99
|
+
self.appended_data = False
|
|
100
|
+
self.appended_data_arrays = None
|
|
101
|
+
self.is_valid_xml = True
|
|
102
|
+
|
|
103
|
+
# process vtk file
|
|
104
|
+
self.parse_xml_structure()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def parse(self):
|
|
109
|
+
"""
|
|
110
|
+
Parses the file based on its type and selects the appropriate parsing method.
|
|
111
|
+
|
|
112
|
+
This method determines the type of grid data from the file, such as
|
|
113
|
+
'UnstructuredGrid', 'StructuredGrid', 'RectilinearGrid', 'ImageData', or 'PolyData',
|
|
114
|
+
and delegates the parsing task to the corresponding specialized parsing method.
|
|
115
|
+
It encapsulates the logic for selecting an appropriate parser and retrieving
|
|
116
|
+
the data.
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
data : Any
|
|
121
|
+
Parsed data resulting from the specific parsing logic based on the grid type.
|
|
122
|
+
"""
|
|
123
|
+
# select correct parser
|
|
124
|
+
if self.file_type == 'UnstructuredGrid':
|
|
125
|
+
data = self.parse_unstructured()
|
|
126
|
+
|
|
127
|
+
elif self.file_type == 'StructuredGrid':
|
|
128
|
+
data = self.parse_structured()
|
|
129
|
+
|
|
130
|
+
elif self.file_type == 'RectilinearGrid':
|
|
131
|
+
data = self.parse_rectilinear()
|
|
132
|
+
|
|
133
|
+
elif self.file_type == 'ImageData':
|
|
134
|
+
data = self.parse_imagedata()
|
|
135
|
+
|
|
136
|
+
elif self.file_type == 'PolyData':
|
|
137
|
+
data = self.parse_polydata()
|
|
138
|
+
|
|
139
|
+
return data
|
|
140
|
+
|
|
141
|
+
def parse_xml_structure(self):
|
|
142
|
+
"""
|
|
143
|
+
Extract the key metadata from the XML file.
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
None
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
_raw_file_contents_bytes = self.file_path.read_bytes()
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
# parse a valid xml file
|
|
154
|
+
self._vtk_xml_file = xmltodict.parse(_raw_file_contents_bytes)['VTKFile']
|
|
155
|
+
|
|
156
|
+
# check for appended data
|
|
157
|
+
try:
|
|
158
|
+
self.appended_data_bstr = self._vtk_xml_file['AppendedData']['#text'].strip('_')
|
|
159
|
+
self.binary_data_encoding = self._vtk_xml_file['AppendedData']['@encoding']
|
|
160
|
+
self.appended_data = True
|
|
161
|
+
|
|
162
|
+
except:
|
|
163
|
+
self.appended_data_bstr = None
|
|
164
|
+
self.binary_data_encoding = None
|
|
165
|
+
|
|
166
|
+
except:
|
|
167
|
+
# invalid xml where raw binary is appended to file
|
|
168
|
+
self.is_valid_xml = False
|
|
169
|
+
self.appended_data = True
|
|
170
|
+
self.binary_data_encoding = 'raw'
|
|
171
|
+
|
|
172
|
+
binary_parts_a = _raw_file_contents_bytes.split(b'<AppendedData encoding="raw">\n')
|
|
173
|
+
binary_parts_b = binary_parts_a[1].split(b'</AppendedData>')
|
|
174
|
+
|
|
175
|
+
reconstituted_file = binary_parts_a[0] + b'<AppendedData encoding="raw">\n' + b'\n </AppendedData>\n </VTKFile>\n'
|
|
176
|
+
self._vtk_xml_file = xmltodict.parse(reconstituted_file)['VTKFile']
|
|
177
|
+
|
|
178
|
+
self.appended_data_bstr = binary_parts_b[0].split(b' _', 1)[1]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# set file descriptors
|
|
182
|
+
self.file_type = self._vtk_xml_file['@type']
|
|
183
|
+
self.file_version = float(self._vtk_xml_file['@version'])
|
|
184
|
+
self.byte_order = self._vtk_xml_file['@byte_order'].lower()
|
|
185
|
+
if self.file_version < 1:
|
|
186
|
+
self.byte_count_type = 'Uint32'
|
|
187
|
+
else:
|
|
188
|
+
self.byte_count_type = self._vtk_xml_file['@header_type']
|
|
189
|
+
self.byte_count_format = _parse_bytecount_type(self.byte_count_type, self.byte_order)
|
|
190
|
+
try:
|
|
191
|
+
self.data_format = next(dict_extract_generator('@format', self._vtk_xml_file[self.file_type]['Piece']))
|
|
192
|
+
except StopIteration:
|
|
193
|
+
self.data_format = None
|
|
194
|
+
|
|
195
|
+
# split appended data now so only dictionary look-up required later
|
|
196
|
+
if self.appended_data:
|
|
197
|
+
appended_offset_keys = get_recursively(self._vtk_xml_file[self.file_type]['Piece'], '@offset')
|
|
198
|
+
if self.binary_data_encoding == 'base64':
|
|
199
|
+
appended_offsets = [int(x) for x in appended_offset_keys]
|
|
200
|
+
data_arrays = [self.appended_data_bstr[i:j] for i, j in zip(appended_offsets, appended_offsets[1:] + [None])]
|
|
201
|
+
|
|
202
|
+
# decode base64 data now instead of later
|
|
203
|
+
decoded_data_arrays = []
|
|
204
|
+
for array in data_arrays:
|
|
205
|
+
decoded_data_arrays.append(pybase64.b64decode(array))
|
|
206
|
+
|
|
207
|
+
data_arrays = decoded_data_arrays
|
|
208
|
+
|
|
209
|
+
# collate arrays
|
|
210
|
+
self.appended_data_arrays = dict(zip(appended_offset_keys, data_arrays))
|
|
211
|
+
|
|
212
|
+
elif self.binary_data_encoding == 'raw':
|
|
213
|
+
current_offset = 0
|
|
214
|
+
data_arrays = {}
|
|
215
|
+
# looping over each array with exact size ensure no rubbish data at end is included
|
|
216
|
+
for offset in appended_offset_keys:
|
|
217
|
+
arr_size = struct.unpack_from(self.byte_count_format[0], self.appended_data_bstr[current_offset:])
|
|
218
|
+
data_start = current_offset
|
|
219
|
+
data_end = current_offset + self.byte_count_format[1] + arr_size[0]
|
|
220
|
+
data_arrays[offset] = self.appended_data_bstr[data_start:data_end]
|
|
221
|
+
current_offset += self.byte_count_format[1] + arr_size[0]
|
|
222
|
+
|
|
223
|
+
# collate arrays
|
|
224
|
+
self.appended_data_arrays = data_arrays
|
|
225
|
+
|
|
226
|
+
else:
|
|
227
|
+
# set empty arrays
|
|
228
|
+
self.appended_data_arrays = {}
|
|
229
|
+
|
|
230
|
+
# set data flags
|
|
231
|
+
try:
|
|
232
|
+
self._vtk_xml_file[self.file_type]['Piece']['PointData']
|
|
233
|
+
self.point_data = True
|
|
234
|
+
except:
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
self._vtk_xml_file[self.file_type]['Piece']['CellData']
|
|
239
|
+
self.cell_data = True
|
|
240
|
+
except:
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
self._vtk_xml_file[self.file_type]['Piece']['FieldData']
|
|
245
|
+
self.field_data = True
|
|
246
|
+
except:
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
def parse_imagedata(self):
|
|
250
|
+
"""
|
|
251
|
+
Reads and processes image data from the provided file and appended data.
|
|
252
|
+
|
|
253
|
+
The function extracts grid topology information, including extents, origin,
|
|
254
|
+
spacing, and direction, from the file's `ImageData` attribute. It then
|
|
255
|
+
retrieves the data arrays (cell data, field data, and point data) by
|
|
256
|
+
processing the `ImageData` attribute in conjunction with the appended data.
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
ImageData
|
|
262
|
+
An instance of the `ImageData` class holding the extracted data arrays
|
|
263
|
+
(cell data, field data, point data) and the grid topology information.
|
|
264
|
+
"""
|
|
265
|
+
grid_topology = Grid(whole_extents=np.fromstring(self._vtk_xml_file[self.file_type]['@WholeExtent'], dtype=int, sep=' '),
|
|
266
|
+
origin=np.fromstring(self._vtk_xml_file[self.file_type]['@Origin'], dtype=np.float32, sep=' '),
|
|
267
|
+
spacing=np.fromstring(self._vtk_xml_file[self.file_type]['@Spacing'], dtype=np.float32, sep=' '),
|
|
268
|
+
direction=np.fromstring(self._vtk_xml_file[self.file_type]['@Direction'], dtype=np.float32, sep=' '))
|
|
269
|
+
|
|
270
|
+
# Get Data Arrays
|
|
271
|
+
field_data = self.get_data('FieldData')
|
|
272
|
+
cell_data = self.get_data('CellData')
|
|
273
|
+
point_data = self.get_data('PointData')
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
return ImageData(point_data=point_data, cell_data=cell_data, field_data=field_data, grid=grid_topology)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def parse_rectilinear(self):
|
|
280
|
+
"""
|
|
281
|
+
Reads and processes rectilinear grid data from the given file and appends
|
|
282
|
+
additional data if provided.
|
|
283
|
+
|
|
284
|
+
The function extracts grid coordinates, topology, and associated data arrays
|
|
285
|
+
such as cell data, field data, and point data. It consolidates these into a
|
|
286
|
+
RectilinearData object for further use.
|
|
287
|
+
|
|
288
|
+
Returns
|
|
289
|
+
-------
|
|
290
|
+
RectilinearData
|
|
291
|
+
A consolidated object representing the rectilinear grid, including
|
|
292
|
+
coordinates, topology, cell data, field data, and point data.
|
|
293
|
+
"""
|
|
294
|
+
# read coordinates - always present
|
|
295
|
+
grid_topology = self.recover_grid_coordinates()
|
|
296
|
+
|
|
297
|
+
# Get Data Arrays
|
|
298
|
+
field_data = self.get_data('FieldData')
|
|
299
|
+
cell_data = self.get_data('CellData')
|
|
300
|
+
point_data = self.get_data('PointData')
|
|
301
|
+
|
|
302
|
+
return RectilinearData(point_data=point_data, cell_data=cell_data, field_data=field_data,
|
|
303
|
+
coordinates=grid_topology)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def parse_structured(self):
|
|
307
|
+
"""
|
|
308
|
+
Reads and processes a structured grid from a given data file and appends relevant data arrays to the result.
|
|
309
|
+
|
|
310
|
+
The function extracts structured grid information such as grid points, whole extents, and associated
|
|
311
|
+
data arrays (cell data, field data, and point data) from the provided data file .
|
|
312
|
+
Returns an instance of `StructuredData` containing the processed information.
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
Returns
|
|
316
|
+
-------
|
|
317
|
+
StructuredData
|
|
318
|
+
An instance of `StructuredData` containing point data, cell data, field data, grid points,
|
|
319
|
+
and grid whole extents.
|
|
320
|
+
|
|
321
|
+
"""
|
|
322
|
+
# get point coordinates
|
|
323
|
+
raw_points = self.get_data_arrays(self._vtk_xml_file[self.file_type]['Piece']['Points']['DataArray'])
|
|
324
|
+
points = _determine_points_key(raw_points)
|
|
325
|
+
|
|
326
|
+
# read coordinates - always present
|
|
327
|
+
whole_extents = np.fromstring(self._vtk_xml_file[self.file_type]['@WholeExtent'], sep=' ')
|
|
328
|
+
|
|
329
|
+
# Get Data Arrays
|
|
330
|
+
field_data = self.get_data('FieldData')
|
|
331
|
+
cell_data = self.get_data('CellData')
|
|
332
|
+
point_data = self.get_data('PointData')
|
|
333
|
+
|
|
334
|
+
return StructuredData(point_data=point_data, cell_data=cell_data, field_data=field_data, points=points,
|
|
335
|
+
whole_extents=whole_extents)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def parse_unstructured(self):
|
|
339
|
+
"""
|
|
340
|
+
Reads unstructured data from a provided file and returns it as an `UnstructuredGrid`.
|
|
341
|
+
|
|
342
|
+
The function retrieves point coordinates, topology, and data arrays, including cell
|
|
343
|
+
data, field data, and point data, from the unstructured grid file.
|
|
344
|
+
|
|
345
|
+
This is useful for loading and processing unstructured grid representations in computational geometry
|
|
346
|
+
or scientific computing applications.
|
|
347
|
+
|
|
348
|
+
Returns
|
|
349
|
+
-------
|
|
350
|
+
UnstructuredGrid
|
|
351
|
+
A data structure containing points, cells, point data, cell data,
|
|
352
|
+
and field data for the unstructured grid.
|
|
353
|
+
"""
|
|
354
|
+
piece_ = self._vtk_xml_file[self.file_type]['Piece']
|
|
355
|
+
|
|
356
|
+
# get point coordinates
|
|
357
|
+
raw_points = self.get_data_arrays(piece_['Points']['DataArray'])
|
|
358
|
+
points = _determine_points_key(raw_points)
|
|
359
|
+
|
|
360
|
+
# get cell topology
|
|
361
|
+
try:
|
|
362
|
+
topology = self.get_data_arrays(piece_['Cells']['DataArray'])
|
|
363
|
+
except:
|
|
364
|
+
topology = self.get_data_arrays(piece_['Cells']['Array'])
|
|
365
|
+
|
|
366
|
+
cell_topology = Cell(connectivity=topology['connectivity'], offsets=topology['offsets'],
|
|
367
|
+
types=topology['types'])
|
|
368
|
+
|
|
369
|
+
# Get Data Arrays
|
|
370
|
+
_fielddata = self.get_data('FieldData')
|
|
371
|
+
_celldata = self.get_data('CellData')
|
|
372
|
+
_pointdata = self.get_data('PointData')
|
|
373
|
+
|
|
374
|
+
field_data = DotWiz(_fielddata) if _fielddata else None
|
|
375
|
+
cell_data = DotWiz(_celldata) if _celldata else None
|
|
376
|
+
point_data = DotWiz(_pointdata) if _pointdata else None
|
|
377
|
+
|
|
378
|
+
return UnstructuredGrid(points=points, cells=cell_topology,
|
|
379
|
+
point_data=point_data, cell_data=cell_data, field_data=field_data)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def parse_polydata(self):
|
|
383
|
+
"""
|
|
384
|
+
Parses `PolyData` from a provided dictionary object and additional appended binary data.
|
|
385
|
+
|
|
386
|
+
The function retrieves point coordinates, topology, and data arrays, including cell data, field data, and point
|
|
387
|
+
data from the PolyData file.
|
|
388
|
+
|
|
389
|
+
This is useful for loading and processing unstructured grid representations in computational geometry
|
|
390
|
+
or scientific computing applications.
|
|
391
|
+
|
|
392
|
+
Returns
|
|
393
|
+
-------
|
|
394
|
+
PolyData
|
|
395
|
+
An instance of `PolyData` containing the parsed point coordinates, topology (vertices, lines, strips,
|
|
396
|
+
including cell data, field data, and point data.
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
"""
|
|
400
|
+
# read topology - always present
|
|
401
|
+
piece_ = self._vtk_xml_file[self.file_type]['Piece']
|
|
402
|
+
|
|
403
|
+
# get poly counts
|
|
404
|
+
num_points = int(piece_['@NumberOfPoints'])
|
|
405
|
+
num_verts = int(piece_['@NumberOfVerts'])
|
|
406
|
+
num_lines = int(piece_['@NumberOfLines'])
|
|
407
|
+
num_strips = int(piece_['@NumberOfStrips'])
|
|
408
|
+
num_polys = int(piece_['@NumberOfPolys'])
|
|
409
|
+
|
|
410
|
+
# get topology
|
|
411
|
+
# get point coordinates
|
|
412
|
+
points = None
|
|
413
|
+
if num_points > 0:
|
|
414
|
+
raw_points = self.get_data_arrays(piece_['Points']['DataArray'])
|
|
415
|
+
points = _determine_points_key(raw_points)
|
|
416
|
+
|
|
417
|
+
verts = None
|
|
418
|
+
lines = None
|
|
419
|
+
strips = None
|
|
420
|
+
polys = None
|
|
421
|
+
|
|
422
|
+
# get vertices
|
|
423
|
+
if num_verts > 0:
|
|
424
|
+
vert_topology = self.get_data_arrays(piece_['Verts']['DataArray'])
|
|
425
|
+
verts = PolyDataTopology(connectivity=vert_topology['connectivity'], offsets=vert_topology['offsets'])
|
|
426
|
+
|
|
427
|
+
# get lines
|
|
428
|
+
if num_lines > 0:
|
|
429
|
+
line_topology = self.get_data_arrays(piece_['Lines']['DataArray'])
|
|
430
|
+
lines = PolyDataTopology(connectivity=line_topology['connectivity'], offsets=line_topology['offsets'])
|
|
431
|
+
|
|
432
|
+
# get strips
|
|
433
|
+
if num_strips > 0:
|
|
434
|
+
strip_topology = self.get_data_arrays(piece_['Strips']['DataArray'])
|
|
435
|
+
strips = PolyDataTopology(connectivity=strip_topology['connectivity'], offsets=strip_topology['offsets'])
|
|
436
|
+
|
|
437
|
+
# get polys
|
|
438
|
+
if num_polys > 0:
|
|
439
|
+
poly_topology = self.get_data_arrays(piece_['Polys']['DataArray'])
|
|
440
|
+
polys = PolyDataTopology(connectivity=poly_topology['connectivity'], offsets=poly_topology['offsets'])
|
|
441
|
+
|
|
442
|
+
# Get Data Arrays
|
|
443
|
+
field_data = self.get_data('FieldData')
|
|
444
|
+
cell_data = self.get_data('CellData')
|
|
445
|
+
point_data = self.get_data('PointData')
|
|
446
|
+
|
|
447
|
+
return PolyData(points=points, verts=verts, lines=lines, strips=strips, polys=polys,
|
|
448
|
+
point_data=point_data, cell_data=cell_data, field_data=field_data)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def get_data(self, data_type):
|
|
452
|
+
"""
|
|
453
|
+
Retrieve data arrays from a VTK XML file.
|
|
454
|
+
|
|
455
|
+
This method extracts and recovers data arrays for a given data type in the `Piece` element of a VTK XML file.
|
|
456
|
+
If no data is present in a specific section, the corresponding return value will be `None`.
|
|
457
|
+
|
|
458
|
+
Returns
|
|
459
|
+
-------
|
|
460
|
+
data: The extracted data arrays from the `PointData` section or `None` if unavailable.
|
|
461
|
+
"""
|
|
462
|
+
piece_ = self._vtk_xml_file[self.file_type]['Piece']
|
|
463
|
+
|
|
464
|
+
if data_type in piece_:
|
|
465
|
+
if piece_[data_type]:
|
|
466
|
+
data_ = piece_[data_type]
|
|
467
|
+
# Try to get data from DataArray, fall back to Array if needed
|
|
468
|
+
try:
|
|
469
|
+
return self.get_data_arrays(data_['DataArray'])
|
|
470
|
+
except KeyError:
|
|
471
|
+
return self.get_data_arrays(data_['Array'])
|
|
472
|
+
|
|
473
|
+
else:
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def decode_byte_data(self, data, data_type):
|
|
478
|
+
"""Helper to decode input data based on encoding type."""
|
|
479
|
+
data_size = struct.unpack_from(self.byte_count_format[0], data)[0]
|
|
480
|
+
|
|
481
|
+
if data_type != 's':
|
|
482
|
+
dtype = self.decode_np_byteorder(data_type)
|
|
483
|
+
return np.frombuffer(data[self.byte_count_format[1]:self.byte_count_format[1] + data_size], dtype=dtype)
|
|
484
|
+
else:
|
|
485
|
+
dtype = self.decode_str_byteorder(data_type, data_size)
|
|
486
|
+
data_strs = struct.unpack(dtype, data[self.byte_count_format[1]:])[0]
|
|
487
|
+
return data_strs.decode('utf8').rstrip('\x00').split('\x00')
|
|
488
|
+
|
|
489
|
+
def decode_array(self, data, data_type, offset=None):
|
|
490
|
+
"""
|
|
491
|
+
Decode data into a NumPy array based on the given format type and parameters.
|
|
492
|
+
|
|
493
|
+
This function decodes data from either an ASCII-encoded string, a binary Base64
|
|
494
|
+
encoded string, or an appended Base64 encoded source, transforming it into a
|
|
495
|
+
NumPy array with the specified data type. The decoding process varies based on
|
|
496
|
+
the selected format type.
|
|
497
|
+
|
|
498
|
+
Parameters
|
|
499
|
+
----------
|
|
500
|
+
data : str
|
|
501
|
+
Input data that is either ASCII-encoded or Base64-encoded.
|
|
502
|
+
data_type : type
|
|
503
|
+
The desired NumPy data type for the resulting array.
|
|
504
|
+
offset : Optional[any], default=None
|
|
505
|
+
Key used to locate the required segment in the appended_data when
|
|
506
|
+
the 'appended' format type is used.
|
|
507
|
+
|
|
508
|
+
Returns
|
|
509
|
+
-------
|
|
510
|
+
numpy.ndarray
|
|
511
|
+
A NumPy array containing the decoded data as per the specified
|
|
512
|
+
format and parameters.
|
|
513
|
+
|
|
514
|
+
"""
|
|
515
|
+
if self.data_format == 'ascii':
|
|
516
|
+
if data_type != 's':
|
|
517
|
+
return np.fromstring(data, dtype=data_type, sep=' ')
|
|
518
|
+
else:
|
|
519
|
+
chars = list(map(chr, map(int, data.split())))
|
|
520
|
+
return ''.join(chars).rstrip('\x00').split('\x00')
|
|
521
|
+
|
|
522
|
+
elif self.data_format == 'binary':
|
|
523
|
+
binary_data = pybase64.b64decode(data)
|
|
524
|
+
return self.decode_byte_data(binary_data, data_type)
|
|
525
|
+
|
|
526
|
+
elif self.data_format == 'appended':
|
|
527
|
+
if self.appended_data_bstr is None:
|
|
528
|
+
raise ValueError("Appended input data is required for 'appended' format.")
|
|
529
|
+
return self.decode_byte_data(self.appended_data_arrays[offset], data_type)
|
|
530
|
+
else:
|
|
531
|
+
raise ValueError(f"Unknown format type: {self.data_format}")
|
|
532
|
+
|
|
533
|
+
@staticmethod
|
|
534
|
+
def reshape_array(array, components):
|
|
535
|
+
"""Reshape array if multiple components are present."""
|
|
536
|
+
return array.reshape(-1, components) if components > 1 else array
|
|
537
|
+
|
|
538
|
+
def extract_data_array(self, data):
|
|
539
|
+
"""
|
|
540
|
+
Extract an array and its associated name from the provided data structure.
|
|
541
|
+
|
|
542
|
+
This function processes a structured data input, extracting a named data array
|
|
543
|
+
and decoding it using specific parameters. If multiple components are defined,
|
|
544
|
+
the array will be reshaped accordingly. The `dtype` and `start_index` values
|
|
545
|
+
are derived from specific fields in the input data.
|
|
546
|
+
|
|
547
|
+
Parameters
|
|
548
|
+
----------
|
|
549
|
+
data : dict
|
|
550
|
+
A dictionary containing structured data with keys such as '@Name', '@type',
|
|
551
|
+
'@NumberOfComponents', '@NumberOfTuples', '#text', and '@offset'.
|
|
552
|
+
|
|
553
|
+
Returns
|
|
554
|
+
-------
|
|
555
|
+
tuple
|
|
556
|
+
A tuple containing two elements:
|
|
557
|
+
- str: The name of the data array extracted from the '@Name' key of the
|
|
558
|
+
input data. If not provided, defaults to 'unknown_name'.
|
|
559
|
+
- numpy.ndarray: The decoded and optionally reshaped data array.
|
|
560
|
+
|
|
561
|
+
"""
|
|
562
|
+
# Extract common data fields
|
|
563
|
+
data_name = data.get('@Name', 'unknown_name')
|
|
564
|
+
dtype = self.parse_data_type(data.get('@type', ''))
|
|
565
|
+
num_components = int(data.get('@NumberOfComponents', data.get('@NumberOfTuples', 1)))
|
|
566
|
+
data_text = data.get('#text', '')
|
|
567
|
+
|
|
568
|
+
# Decode array using helper function
|
|
569
|
+
decoded_array = self.decode_array(data=data_text, data_type=dtype, offset=data.get('@offset'))
|
|
570
|
+
|
|
571
|
+
# Reshape array based on components and add to data_arrays
|
|
572
|
+
if num_components > 1:
|
|
573
|
+
decoded_array = self.reshape_array(decoded_array, num_components)
|
|
574
|
+
|
|
575
|
+
return data_name, decoded_array
|
|
576
|
+
|
|
577
|
+
@staticmethod
|
|
578
|
+
def parse_data_type(data_type):
|
|
579
|
+
"""Resolves the NumPy dtype and start index based on data type."""
|
|
580
|
+
type_mapping = {
|
|
581
|
+
'int': np.int64,
|
|
582
|
+
'float': np.float32,
|
|
583
|
+
'double': np.float64,
|
|
584
|
+
'float16': np.float16,
|
|
585
|
+
'float32': np.float32,
|
|
586
|
+
'float64': np.float64,
|
|
587
|
+
'int8': np.int8,
|
|
588
|
+
'int16': np.int16,
|
|
589
|
+
'int32': np.int32,
|
|
590
|
+
'int64': np.int64,
|
|
591
|
+
'uint8': np.uint8,
|
|
592
|
+
'uint16': np.uint16,
|
|
593
|
+
'uint32': np.uint32,
|
|
594
|
+
'uint64': np.uint64,
|
|
595
|
+
'string': 's'
|
|
596
|
+
}
|
|
597
|
+
return type_mapping.get(data_type.lower(), None)
|
|
598
|
+
|
|
599
|
+
def decode_np_byteorder(self, dtype):
|
|
600
|
+
"""Helper to adjust dtype based on byte order."""
|
|
601
|
+
new_byteorder = LITTLE_ENDIAN if self.byte_order in ['<', 'littleendian'] else BIG_ENDIAN
|
|
602
|
+
return np.dtype(dtype).newbyteorder(new_byteorder)
|
|
603
|
+
|
|
604
|
+
def decode_str_byteorder(self, dtype, data_size):
|
|
605
|
+
"""Helper to adjust dtype based on byte order."""
|
|
606
|
+
new_byteorder = LITTLE_ENDIAN if self.byte_order in ['<', 'littleendian'] else BIG_ENDIAN
|
|
607
|
+
return new_byteorder + str(data_size) + dtype
|
|
608
|
+
|
|
609
|
+
def recover_grid_coordinates(self):
|
|
610
|
+
"""
|
|
611
|
+
Recover grid coordinates from the given data.
|
|
612
|
+
|
|
613
|
+
This function extracts grid coordinates from a nested dictionary structure
|
|
614
|
+
if they are available. It checks for the existence of the 'Coordinates' key
|
|
615
|
+
within the 'Piece' dictionary of the input data and retrieves the corresponding
|
|
616
|
+
data using the provided `recover_data_arrays` function. If no coordinates are
|
|
617
|
+
found or the 'Coordinates' key is not present, it returns `None`.
|
|
618
|
+
|
|
619
|
+
Returns
|
|
620
|
+
-------
|
|
621
|
+
list or None
|
|
622
|
+
The recovered grid coordinates in the form of a `GridCoordinates` instance if available,
|
|
623
|
+
or `None` if the coordinates are not present in the input data.
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
"""
|
|
627
|
+
try:
|
|
628
|
+
xml_data = self._vtk_xml_file[self.file_type]['Piece']['Coordinates']['DataArray']
|
|
629
|
+
|
|
630
|
+
data_arrays = self.get_data_arrays(xml_data)
|
|
631
|
+
|
|
632
|
+
grid_topology = GridCoordinates(x=data_arrays[[s for s in data_arrays if 'x' in s.lower()][0]],
|
|
633
|
+
y=data_arrays[[s for s in data_arrays if 'y' in s.lower()][0]],
|
|
634
|
+
z=data_arrays[[s for s in data_arrays if 'z' in s.lower()][0]],
|
|
635
|
+
whole_extents=np.fromstring(self._vtk_xml_file[self.file_type]['@WholeExtent'], sep=' '))
|
|
636
|
+
return grid_topology
|
|
637
|
+
|
|
638
|
+
except:
|
|
639
|
+
return None
|
|
640
|
+
|
|
641
|
+
def get_data_arrays(self, xml_data):
|
|
642
|
+
"""
|
|
643
|
+
Recovers and organizes data arrays by iterating over XML data and appending it
|
|
644
|
+
to an existing structure for return.
|
|
645
|
+
|
|
646
|
+
This function processes XML-like input data in a dictionary, organizes them into a dictionary,
|
|
647
|
+
and combines their contents with a provided data structure.
|
|
648
|
+
|
|
649
|
+
Parameters
|
|
650
|
+
----------
|
|
651
|
+
xml_data : dictionary
|
|
652
|
+
The data to be processed. It can either be a list of XML-like items or a
|
|
653
|
+
single XML-like item.
|
|
654
|
+
|
|
655
|
+
Returns
|
|
656
|
+
-------
|
|
657
|
+
dict
|
|
658
|
+
A dictionary containing the organized data arrays.
|
|
659
|
+
"""
|
|
660
|
+
if type(xml_data) == list:
|
|
661
|
+
data_arrays = {}
|
|
662
|
+
for data in xml_data:
|
|
663
|
+
name, array = self.extract_data_array(data)
|
|
664
|
+
data_arrays[name] = array
|
|
665
|
+
|
|
666
|
+
else:
|
|
667
|
+
name, array = self.extract_data_array(xml_data)
|
|
668
|
+
data_arrays = {name: array}
|
|
669
|
+
|
|
670
|
+
return data_arrays
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def convert_to_vtkhdf(xml_file_path):
|
|
674
|
+
"""
|
|
675
|
+
Convert a VTK XML file to an equivalent VTKHDF file.
|
|
676
|
+
|
|
677
|
+
Parameters
|
|
678
|
+
----------
|
|
679
|
+
xml_file_path : str
|
|
680
|
+
Path to XML file, including file extension. This file will be converted to a VTKHDF file of the same type.
|
|
681
|
+
|
|
682
|
+
Warnings
|
|
683
|
+
--------
|
|
684
|
+
Currently RectilinearData and StructuredGrid are not supported by the VTK format as this is not yet finalised.
|
|
685
|
+
The file formats used here are the proposed formats.
|
|
686
|
+
|
|
687
|
+
"""
|
|
688
|
+
data = Reader(xml_file_path).parse()
|
|
689
|
+
|
|
690
|
+
data.write_vtkhdf_file(xml_file_path.split['.'][0], file_format='vtkhdf')
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
# Function to read data using the Reader class
|
|
694
|
+
def read_vtkxml_data(filename):
|
|
695
|
+
"""
|
|
696
|
+
Reads and parses data from the specified file.
|
|
697
|
+
|
|
698
|
+
This function initializes a `Reader` object with the provided filename
|
|
699
|
+
and invokes its `parse` method to process and return the parsed data.
|
|
700
|
+
|
|
701
|
+
Parameters
|
|
702
|
+
----------
|
|
703
|
+
filename : str
|
|
704
|
+
Path to the file that needs to be read and parsed.
|
|
705
|
+
|
|
706
|
+
Returns
|
|
707
|
+
-------
|
|
708
|
+
Any
|
|
709
|
+
Parsed data produced by the `Reader.parse()` method.
|
|
710
|
+
"""
|
|
711
|
+
reader = Reader(filename)
|
|
712
|
+
return reader.parse()
|