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/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()