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/writer/vtkhdf.py ADDED
@@ -0,0 +1,1184 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ VTKHDF Writer Module.
4
+
5
+ Includes all functions for writing VTKHDF files, including image data, poly data,
6
+ unstructured grid, structured grid, and rectilinear grid. Also includes functions
7
+ for writing additional metadata and creating multiblock datasets.
8
+
9
+ Note: This module writes structured grids and rectilinear grids in a VTKHDF format
10
+ although the definition is not yet formally included in the VTKHDF specification.
11
+
12
+ """
13
+
14
+ __author__ = 'J.P. Morrissey'
15
+ __copyright__ = 'Copyright 2022-2025'
16
+ __maintainer__ = 'J.P. Morrissey'
17
+ __email__ = 'morrissey.jp@gmail.com'
18
+ __status__ = 'Development'
19
+
20
+ # Standard library
21
+ from abc import ABCMeta, abstractmethod
22
+ from pathlib import Path
23
+
24
+ # Imports
25
+ import h5py
26
+ import numpy as np
27
+
28
+ # Local imports
29
+ from ..utilities import flatten
30
+
31
+ # Global metadata
32
+ fType = 'f'
33
+ idType = 'i8'
34
+ charType = 'uint8'
35
+
36
+ __all__ = [
37
+ 'VTKHDFMultiBlockWriter',
38
+ 'VTKHDFImageDataWriter',
39
+ 'VTKHDFUnstructuredGridWriter',
40
+ 'VTKHDFPolyDataWriter',
41
+ 'VTKHDFStructuredGridWriter',
42
+ 'VTKHDFRectilinearGridWriter'
43
+ ]
44
+
45
+ class VTKHDFWriterBase(metaclass=ABCMeta):
46
+ """
47
+ Base class for writing VTKHDF files.
48
+
49
+ This class provides the basic structure and methods for writing VTKHDF files,
50
+ including methods for writing metadata, topology, and data arrays. It is intended
51
+ to be subclassed for specific VTKHDF dataset types such as ImageData, PolyData,
52
+ UnstructuredGrid, and StructuredGrid.
53
+
54
+ Parameters
55
+ ----------
56
+ filename : str
57
+ The name of the file to write the VTKHDF dataset to. Should end with '.vtkhdf'.
58
+ version : tuple, optional
59
+ The version of the VTKHDF format to use. Default is (2, 2).
60
+ additional_metadata : dict, optional
61
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
62
+ that should be stored in the file, such as attributes or custom data.
63
+
64
+ Attributes
65
+ ----------
66
+ field_data : dict
67
+ A dictionary containing field data arrays to be written. Keys are the names of the data arrays,
68
+ and values are the corresponding numpy arrays.
69
+ point_data : dict
70
+ A dictionary containing point data arrays to be written. Keys are the names of the data arrays,
71
+ and values are the corresponding numpy arrays.
72
+ cell_data : dict
73
+ A dictionary containing cell data arrays to be written. Keys are the names of the data arrays,
74
+ and values are the corresponding numpy arrays.
75
+ root : h5py.Group
76
+ The root group of the HDF5 file where the VTKHDF dataset will be written.
77
+ path : str
78
+ The name of the file to write the VTKHDF dataset to, including the '.vtkhdf' extension.
79
+ version : tuple
80
+ The version of the VTKHDF format being used.
81
+ npoints : int
82
+ The number of points in the dataset, calculated from the extents for structured grids.
83
+ ncells : int
84
+ The number of cells in the dataset, calculated from the extents for structured grids.
85
+ nverts : int
86
+ The number of vertices in the dataset, to be calculated from the data.
87
+ nlines : int
88
+ The number of lines in the dataset, to be calculated from the data.
89
+ nstrips : int
90
+ The number of strips in the dataset, to be calculated from the data.
91
+ npolys : int
92
+ The number of polygons in the dataset, to be calculated from the data.
93
+ additional_metadata : dict
94
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
95
+ that should be stored in the file, such as attributes or custom data.
96
+
97
+ """
98
+ supported_versions = [(2, 2), (2, 3), (2, 4)]
99
+ extension = '.vtkhdf'
100
+
101
+ def __init__(self, filename, version=(2,2), additional_metadata=None):
102
+ self.field_data = None
103
+ self.point_data = None
104
+ self.cell_data = None
105
+ self.root = None
106
+ self.path = Path(filename).with_suffix(self.extension)
107
+
108
+ if version not in VTKHDFWriterBase.supported_versions:
109
+ raise ValueError("Unsupported VTKHDF version. Supported versions are (2, 2), (2, 3), and (2, 4).")
110
+ self.version = version
111
+
112
+ # set data attributes
113
+ # num points and cells can be calculated from the extents for ImageData, StructuredGrid and RectilinearGrid
114
+ self.npoints = 0
115
+ self.ncells = 0
116
+
117
+ # needs to be calculated from the data first and passed at instantiation
118
+ self.nverts = 0
119
+ self.nlines = 0
120
+ self.nstrips = 0
121
+ self.npolys = 0
122
+
123
+ self.additional_metadata = additional_metadata
124
+
125
+
126
+ def write_additional_metadata(self, dictionary, group_path: str = '/VTKHDF'):
127
+ """
128
+ Recursively write a nested dictionary to an HDF5 file group.
129
+
130
+ Parameters
131
+ ----------
132
+ dictionary : dict
133
+ The dictionary to write to the HDF5 file. It can contain nested dictionaries, lists, numpy arrays,
134
+ or simple scalar values (int, float, str, bool).
135
+ group_path : str, optional
136
+ The path within the HDF5 file where the dictionary should be written.
137
+ Default is '/VTKHDF'.
138
+ """
139
+ if dictionary is None:
140
+ return
141
+
142
+ # Create group if it doesn't exist
143
+ # Handle case where group_path is an h5py.Group
144
+ if isinstance(group_path, h5py.Group):
145
+ current_group = self.root.create_group('additional_data') if self.root.name == '/VTKHDF' else group_path
146
+
147
+ group_path = current_group.name
148
+ else:
149
+ if group_path == '/VTKHDF':
150
+ group_path = 'additional_data'
151
+ current_group = self.root.create_group(group_path)
152
+ else:
153
+ # Create group if it doesn't exist
154
+ try:
155
+ current_group = self.root[group_path]
156
+ except KeyError:
157
+ current_group = self.root.create_group(group_path)
158
+
159
+ # Iterate through dictionary items
160
+ for key, value in dictionary.items():
161
+ # Ensure key is a string
162
+ key = str(key)
163
+
164
+ # Handle different types of values
165
+ if isinstance(value, dict):
166
+ if key == 'attrs':
167
+ # If the key is 'attrs', treat it as attributes
168
+ for attr_key, attr_value in value.items():
169
+ current_group.attrs[attr_key] = attr_value
170
+ else:
171
+ # For nested dictionaries, create a new group and recurse
172
+ new_group_path = f"{group_path}/{key}"
173
+ self.write_additional_metadata(value, new_group_path)
174
+
175
+ elif isinstance(value, (np.ndarray, list, tuple)):
176
+ # Convert lists/tuples to numpy arrays
177
+ if not isinstance(value, np.ndarray):
178
+ value = np.array(value)
179
+
180
+ # Create dataset
181
+ current_group.create_dataset(key, data=value)
182
+
183
+ elif isinstance(value, (int, float, str, bool, np.number)):
184
+ # Store simple scalar types as attributes
185
+ current_group.attrs[key] = value
186
+
187
+ else:
188
+ # Try to convert to string or numpy array for other types
189
+ try:
190
+ current_group.attrs[key] = str(value)
191
+ except:
192
+ try:
193
+ current_group.create_dataset(key, data=np.array(value))
194
+ except:
195
+ print(f"Warning: Could not store value for key {key} of type {type(value)}")
196
+
197
+ @abstractmethod
198
+ def write_topology(self):
199
+ pass
200
+
201
+ def write_vtkhdf_file(self):
202
+ """
203
+ Write the VTKHDF dataset to the specified file.
204
+
205
+ This method creates the root group in the HDF5 file, initializes the CellData, PointData, and FieldData groups,
206
+ and calls the write_topology method to write the dataset topology. It then writes the data arrays for structured
207
+ grids (ImageData, StructuredGrid, RectilinearGrid) or unstructured grids (UnstructuredGrid, PolyData) based on
208
+ the type of dataset being written. Finally, it writes any additional metadata to the file.
209
+
210
+ """
211
+ with h5py.File(self.path, 'w') as f:
212
+ self.root = f.create_group('VTKHDF')
213
+ self.CellData = self.root.create_group("CellData")
214
+ self.PointData = self.root.create_group("PointData")
215
+ self.FieldData = self.root.create_group("FieldData")
216
+ self.write_topology()
217
+ if self.root.attrs['Type'].decode('utf8') == 'ImageData':
218
+ self.write_grid_data_arrays()
219
+ else:
220
+ self.write_nongrid_data_arrays()
221
+ self.write_additional_metadata(self.additional_metadata)
222
+
223
+ def add_structured_data_to_group(self, group, data_shape, dname, darray):
224
+ """
225
+ Add a data array to an HDF5 group as a dataset.
226
+
227
+ Adds a data array to an HDF5 group as a dataset, reshaping it to match the specified data shape and
228
+ accounting for VTK's Fortran-style axis order.
229
+ root : h5py.Group
230
+ The HDF5 group to which the dataset will be added.
231
+ data_shape : tuple or list of int
232
+ The shape of the data (excluding the number of components).
233
+ dname : str
234
+ The name of the dataset to be created within the group.
235
+ darray : numpy.ndarray
236
+ The data array to be stored. Should have shape (N, n_comp) or (N,) if single component.
237
+
238
+
239
+ Notes
240
+ -----
241
+ The function attempts to determine the number of components in the data array. If the array is 1D,
242
+ it assumes a single component. The data is reshaped to match the reversed data shape (to account for
243
+ VTK's Fortran axis order) with the number of components as the last dimension.
244
+ """
245
+ try:
246
+ n_comp = darray.shape[1]
247
+ except:
248
+ n_comp = 1
249
+
250
+ # root.create_dataset(dname, data=darray.reshape([*data_shape, n_comp], order='F'))
251
+ # reverse order of dimensions to account for VTK fortran axis order
252
+ group.create_dataset(dname, data=darray.reshape([*data_shape[::-1], n_comp]))
253
+
254
+ def add_unstructured_group_data(self, group, name, data):
255
+ """
256
+ Add a data array to an HDF5 group as a dataset for unstructured grids.
257
+
258
+ Adds a data array to an HDF5 group as a dataset. If the data is a dictionary, it assumes that
259
+ the dictionary contains data grouped by data array type and creates datasets for each type.
260
+ If the data is not a dictionary, it assumes that it is a single data array and creates a dataset
261
+ with the specified name.
262
+
263
+ Parameters
264
+ ----------
265
+ group : h5py.Group
266
+ The HDF5 group to which the dataset will be added.
267
+ name : str
268
+ The name of the dataset to be created within the group.
269
+ data : numpy.ndarray or dict
270
+ The data array to be stored. If a dictionary, it should contain data grouped by data array type.
271
+
272
+ Notes
273
+ -----
274
+ This method is used for unstructured grids, where the data arrays do not require reshaping like
275
+ structured grids. The data is stored directly in the group as datasets without reshaping.
276
+
277
+ """
278
+ if isinstance(data, dict):
279
+ # if data is a dict, we assume it contains data grouped by data array type
280
+ for darray_name, darray in data.items():
281
+ group.create_dataset(darray_name, data=darray)
282
+ else:
283
+ # if data is not a dict, we assume it is a single data array
284
+ group.create_dataset(name, data=data)
285
+
286
+ def add_structured_group_data(self, group, name, data, datasize):
287
+ """
288
+ Add a data array to an HDF5 group as a dataset for structured grids.
289
+
290
+ Adds a data array to an HDF5 group as a dataset. If the data is a dictionary, it assumes that
291
+ the dictionary contains data grouped by data array type and creates datasets for each type.
292
+ If the data is not a dictionary, it assumes that it is a single data array and creates a dataset
293
+ with the specified name.
294
+
295
+ Parameters
296
+ ----------
297
+ group : h5py.Group
298
+ The HDF5 group to which the dataset will be added.
299
+ name : str
300
+ The name of the dataset to be created within the group.
301
+ data : numpy.ndarray or dict
302
+ The data array to be stored. If a dictionary, it should contain data grouped by data array type.
303
+ datasize : tuple(ints)
304
+ The size of the data array, which is used to reshape the data correctly for structured grids.
305
+
306
+ Notes
307
+ -----
308
+ This method is used for structured grids (ImageData, StructuredGrid, RectilinearGrid), where the data
309
+ arrays need to be reshaped to match the grid structure. The reshaping accounts for VTK's Fortran-style
310
+ axis order. The data is stored in the group as datasets, reshaped to the specified data size.
311
+
312
+
313
+ """
314
+ if isinstance(data, dict):
315
+ # if data is a dict, we assume it contains data grouped by data array type
316
+ for darray_name, darray in data.items():
317
+ self.add_structured_data_to_group(group, datasize, darray_name, darray)
318
+ else:
319
+ # if data is not a dict, we assume it is a single data array
320
+ self.add_structured_data_to_group(group, datasize, name, data)
321
+
322
+ def write_grid_data_arrays(self):
323
+ """
324
+ Write data arrays for structured grids (ImageData, StructuredGrid, RectilinearGrid).
325
+
326
+ This method writes point data, cell data, and field data to the HDF5 file in the appropriate groups.
327
+ It handles the structured nature of the data, ensuring that the data arrays are reshaped correctly
328
+ to match the grid structure. The reshaping accounts for VTK's Fortran-style axis order.
329
+
330
+ The method assumes that the data arrays are provided in a dictionary format, where keys are the names
331
+ of the data arrays and values are the corresponding numpy arrays. If the data arrays are not provided,
332
+ the method will skip writing that type of data.
333
+ """
334
+
335
+ # write cell data if present
336
+ if self.cell_data is not None:
337
+ for name, data in self.cell_data.items():
338
+ self.add_structured_group_data(self.CellData, name, data, self.num_cells)
339
+
340
+ # write point data if present
341
+ if self.point_data is not None:
342
+ for name, data in self.point_data.items():
343
+ self.add_structured_group_data(self.PointData, name, data, self.num_points)
344
+
345
+ # write field data if present
346
+ if self.field_data is not None:
347
+ # field data can be any shape, so we store it directly
348
+ for name, darray in self.field_data.items():
349
+ if isinstance(darray, np.ndarray):
350
+ self.FieldData.create_dataset(name, data=darray)
351
+ else:
352
+ self.FieldData.create_dataset(name, data=np.array([darray]))
353
+
354
+ def write_nongrid_data_arrays(self):
355
+ """
356
+ Write data arrays for unstructured grids (UnstructuredGrid, PolyData).
357
+
358
+ This method writes point data, cell data, and field data to the HDF5 file in the appropriate groups.
359
+ It handles the unstructured nature of the data, ensuring that the data arrays are stored correctly - unstructured data does not require reshaping like structured data.
360
+
361
+ The method assumes that the data arrays are provided in a dictionary format, where keys are the names
362
+ of the data arrays and values are the corresponding numpy arrays. If the data arrays are not provided,
363
+ the method will skip writing that type of data.
364
+ """
365
+
366
+ # write cell data if present
367
+ if self.cell_data is not None:
368
+ for name, data in self.cell_data.items():
369
+ self.add_unstructured_group_data(self.CellData, name, data)
370
+
371
+ # write point data if present
372
+ if self.point_data is not None:
373
+ for name, data in self.point_data.items():
374
+ self.add_unstructured_group_data(self.PointData, name, data)
375
+
376
+ # write field data if present
377
+ if self.field_data is not None:
378
+ # field data can be any shape, so we store it directly
379
+ for name, darray in self.field_data.items():
380
+ if isinstance(darray, np.ndarray):
381
+ self.FieldData.create_dataset(name, data=darray)
382
+ else:
383
+ self.FieldData.create_dataset(name, data=np.array([darray]))
384
+
385
+ @staticmethod
386
+ def _check_array_sizes(array_data):
387
+ """
388
+ Check the size of all data arrays to be written to ensure they are all the same length.
389
+
390
+ Parameters
391
+ ----------
392
+ array_data : dictionary of data arrays to be written.
393
+
394
+ Returns
395
+ -------
396
+ Array Size : Int
397
+
398
+ Raises
399
+ ------
400
+ ValueError : If the sizes of the arrays are not all equal.
401
+
402
+ """
403
+ # flatten dictionary to check sizes more easily
404
+ flattened_arrays = flatten(array_data, parent_key='', separator='_')
405
+
406
+ sizes = []
407
+ for _key, val in flattened_arrays.items():
408
+ if val.ndim == 1:
409
+ sizes.append(val.size)
410
+ else:
411
+ sizes.append(val.shape[0])
412
+
413
+ all_equal = all(sizes)
414
+
415
+ if all_equal:
416
+ return sizes[0]
417
+ else:
418
+ raise ValueError("Warning: Arrays provided are not all the same length. Data not written to file.")
419
+
420
+ # Keep specialised methods for regular XML writing:
421
+ def check_array_sizes_for_cell_data(self, cell_data):
422
+ """
423
+ Compare size of cell data with the number of cells in the file.
424
+
425
+ Parameters
426
+ ----------
427
+ cell_data : dict
428
+ Dictionary containing cell data arrays to be checked.
429
+
430
+ Raises
431
+ -------
432
+ ValueError : If the size of cell data does not match the number of cells.
433
+
434
+ """
435
+ if cell_data is not None:
436
+ cell_data_size = self._check_array_sizes(cell_data)
437
+ if cell_data_size != self.ncells:
438
+ raise ValueError('Cells and cell data sizes do not match')
439
+
440
+ def check_array_sizes_for_point_data(self, point_data):
441
+ """
442
+ Compare size of point data with the number of points in the file.
443
+
444
+ Parameters
445
+ ----------
446
+ point_data : dict
447
+ Dictionary containing point data arrays to be checked.
448
+
449
+ Raises
450
+ -------
451
+ ValueError : If the size of point data does not match the number of points.
452
+ """
453
+ if point_data is not None:
454
+ point_data_size = self._check_array_sizes(point_data)
455
+ if point_data_size != self.npoints:
456
+ raise ValueError('Points and point data sizes do not match')
457
+
458
+
459
+ class VTKHDFMultiBlockWriter(VTKHDFWriterBase):
460
+ """
461
+ A class for writing multiblock datasets in VTKHDF format.
462
+
463
+ This class allows for the creation of a multiblock dataset where each block can be
464
+ a different type of VTK dataset (e.g., UnstructuredGrid, ImageData, PolyData).
465
+ Each block is written as a separate group within the main VTKHDF group, and an assembly
466
+ group is created to link these blocks together.
467
+
468
+ The class inherits from VTKHDFWriterBase and implements the write_vtkhdf_file method to handle
469
+ the specifics of writing multiple blocks to a single VTKHDF file.
470
+ """
471
+ def __init__(self, filename, blocks, additional_metadata=None):
472
+ """
473
+ Initialize the VTKHDFMultiBlockWriter with a path and a dictionary of blocks.
474
+
475
+ Parameters
476
+ ----------
477
+ filename : str
478
+ The name of the file to write the multiblock dataset to. Should end with '.vtkhdf'.
479
+ blocks : dict
480
+ A dictionary where keys are block names and values are instances of VTKHDFWriterBase
481
+ (e.g., VTKHDFUnstructuredGridWriter, VTKHDFImageDataWriter, etc.) that will be written as blocks.
482
+ additional_metadata : dict, optional
483
+ Additional metadata to be written to the VTKHDF file. Default is None.
484
+
485
+ """
486
+ super().__init__(filename, additional_metadata)
487
+ self.blocks = blocks # dict: name -> writer instance
488
+
489
+ def write_vtkhdf_file(self):
490
+ """
491
+ Write the multiblock dataset to the VTKHDF file.
492
+
493
+ This method creates the main VTKHDF group, writes the metadata, and iterates over the blocks
494
+ to write each one as a separate group. It also creates an assembly group that links the blocks
495
+ together using soft links.
496
+ """
497
+ with h5py.File(self.path, 'w', track_order=True) as f:
498
+ root = f.create_group('VTKHDF', track_order=True)
499
+ root.attrs['Version'] = (2, 2)
500
+ ascii_type = 'PartitionedDataSetCollection'.encode('ascii')
501
+ root.attrs.create('Type', ascii_type, dtype=h5py.string_dtype('ascii', len(ascii_type)))
502
+ root.create_group('Assembly', track_order=True)
503
+ self.write_metadata(root)
504
+
505
+ for idx, (blk_name, writer) in enumerate(self.blocks.items()):
506
+ blk = root.create_group(blk_name, track_order=True)
507
+ writer.multiblock_index = idx
508
+ writer.write_vtkhdf_file(root=blk)
509
+ assembly_blk = root['Assembly'].create_group(blk_name, track_order=True)
510
+ assembly_blk[blk_name] = h5py.SoftLink(f'/VTKHDF/{blk_name}')
511
+
512
+
513
+ class VTKHDFImageDataWriter(VTKHDFWriterBase):
514
+ """
515
+ Write ImageData to a file in the VTKHDF format.
516
+
517
+ This class allows for the creation of ImageData datasets, which are structured grids
518
+ defined by their whole extent, origin, spacing, and direction. It inherits from
519
+ VTKHDFWriterBase and implements the write_vtkhdf_file method to handle the specifics of writing
520
+ ImageData to a VTKHDF file.
521
+
522
+ Parameters
523
+ ----------
524
+ filename : str
525
+ The name of the file to write the ImageData to. Should end with '.vtkhdf'.
526
+ whole_extent : list or array-like
527
+ The whole extent of the ImageData, defined as [xmin, xmax, ymin, ymax, zmin, zmax].
528
+ origin : list or array-like
529
+ The origin of the ImageData, defined as [x_origin, y_origin, z_origin].
530
+ spacing : list or array-like
531
+ The spacing of the ImageData, defined as [x_spacing, y_spacing, z_spacing].
532
+ direction : list or array-like, optional
533
+ The direction cosines of the ImageData, defined as a flattened 3x3 matrix.
534
+ If not provided, defaults to the identity matrix (no rotation).
535
+ version : tuple, optional
536
+ The version of the VTKHDF format to use. Default is (2, 2).
537
+ point_data : dict, optional
538
+ A dictionary containing point data arrays to be written. Keys are the names of the data arrays,
539
+ and values are the corresponding numpy arrays.
540
+ cell_data : dict, optional
541
+ A dictionary containing cell data arrays to be written. Keys are the names of the data arrays,
542
+ and values are the corresponding numpy arrays.
543
+ field_data : dict, optional
544
+ A dictionary containing field data arrays to be written. Keys are the names of the data arrays,
545
+ and values are the corresponding numpy arrays.
546
+ additional_metadata : dict, optional
547
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
548
+ that should be stored in the file, such as attributes or custom data.
549
+ multiblock_index : int, optional
550
+ An index for the multiblock dataset, if this ImageData is part of a larger multiblock dataset.
551
+ If not provided, defaults to None.
552
+ """
553
+
554
+ def __init__(self, filename, whole_extent, origin, spacing, direction=None, version=(2, 2),
555
+ point_data=None, cell_data=None, field_data=None, additional_metadata=None, multiblock_index=None):
556
+ """
557
+ Initialise the VTKHDFImageDataWriter with the necessary parameters for ImageData.
558
+
559
+ Parameters
560
+ ----------
561
+ filename : str
562
+ The name of the file to write the ImageData to. Should end with '.vtkhdf'.
563
+ whole_extent : list or array-like
564
+ The whole extent of the ImageData, defined as [xmin, xmax, ymin, ymax, zmin, zmax].
565
+ origin : list or array-like
566
+ The origin of the ImageData, defined as [x_origin, y_origin, z_origin].
567
+ spacing : list or array-like
568
+ The spacing of the ImageData, defined as [x_spacing, y_spacing, z_spacing].
569
+ direction : list or array-like, optional
570
+ The direction cosines of the ImageData, defined as a flattened 3x3 matrix.
571
+ If not provided, defaults to the identity matrix (no rotation).
572
+ version : tuple, optional
573
+ The version of the VTKHDF format to use. Default is (2, 2).
574
+ point_data : dict, optional
575
+ A dictionary containing point data arrays to be written. Keys are the names of the data arrays,
576
+ and values are the corresponding numpy arrays.
577
+ cell_data : dict, optional
578
+ A dictionary containing cell data arrays to be written. Keys are the names of the data arrays,
579
+ and values are the corresponding numpy arrays.
580
+ field_data : dict, optional
581
+ A dictionary containing field data arrays to be written. Keys are the names of the data arrays,
582
+ and values are the corresponding numpy arrays.
583
+ additional_metadata : dict, optional
584
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
585
+ that should be stored in the file, such as attributes or custom data.
586
+ multiblock_index : int, optional
587
+ An index for the multiblock dataset, if this ImageData is part of a larger multiblock dataset.
588
+ If not provided, defaults to None.
589
+ """
590
+ super().__init__(filename, additional_metadata=additional_metadata, version=version)
591
+ self.whole_extent = np.asarray(whole_extent)
592
+ if len(self.whole_extent) != 6:
593
+ raise ValueError("whole_extent must be a list or array of length 6.")
594
+
595
+ self.origin = np.asarray(origin)
596
+ self.spacing = np.asarray(spacing)
597
+ self.direction = np.asarray(direction) if direction is not None else np.eye(3).flatten()
598
+ self.multiblock_index = multiblock_index
599
+
600
+ self.num_cells = (self.whole_extent[1::2] - self.whole_extent[0::2])
601
+ self.num_points = self.num_cells + 1
602
+ self.ncells = np.prod(self.num_cells)
603
+ self.npoints = np.prod(self.num_cells + 1)
604
+
605
+ # do check on provided data sizes before attempting to write
606
+ self.check_array_sizes_for_point_data(point_data)
607
+ self.check_array_sizes_for_cell_data(cell_data)
608
+
609
+ self.point_data = point_data
610
+ self.cell_data = cell_data
611
+ self.field_data = field_data
612
+
613
+ def write_topology(self):
614
+ """
615
+ Write the topology of the ImageData to the HDF5 file.
616
+
617
+ This method creates the necessary attributes and datasets in the HDF5 file to represent
618
+ the ImageData topology, including whole extent, origin, spacing, and direction.
619
+ If a multiblock index is provided, it is stored as an attribute. The version and type of the dataset
620
+ are also set as attributes. The whole extent, origin, spacing, and direction are stored as attributes
621
+ within the root group of the HDF5 file.
622
+
623
+ """
624
+ if self.multiblock_index is not None:
625
+ self.root.attrs.create('Index', self.multiblock_index, dtype=idType)
626
+ self.root.attrs['Version'] = self.version
627
+ ascii_type = 'ImageData'.encode('ascii')
628
+ self.root.attrs.create('Type', ascii_type, dtype=h5py.string_dtype('ascii', len(ascii_type)))
629
+ self.root.attrs.create('WholeExtent', self.whole_extent, dtype=idType)
630
+ self.root.attrs.create('Origin', self.origin, dtype=fType)
631
+ self.root.attrs.create('Spacing', self.spacing, dtype=fType)
632
+ self.root.attrs.create('Direction', self.direction, dtype=fType)
633
+
634
+
635
+ class VTKHDFUnstructuredGridWriter(VTKHDFWriterBase):
636
+ """
637
+ Write UnstructuredGrid datasets to a file in VTKHDF format.
638
+
639
+ This class allows for the creation of UnstructuredGrid datasets, which are defined by their nodes,
640
+ cell types, connectivity, and offsets. It inherits from VTKHDFWriterBase and implements the
641
+ write_vtkhdf_file method to handle the specifics of writing UnstructuredGrid data to a VTKHDF file.
642
+
643
+ Parameters
644
+ ----------
645
+ filename : str
646
+ The name of the file to write the UnstructuredGrid to. Should end with '.vtkhdf'.
647
+ nodes : numpy.ndarray
648
+ An array of nodes defining the vertices of the UnstructuredGrid. Should be of shape (N, 3),
649
+ where N is the number of nodes.
650
+ cell_types : numpy.ndarray
651
+ An array of cell types for the UnstructuredGrid. Should be a 1D array of integers representing
652
+ the types of cells (e.g., VTK_TRIANGLE, VTK_QUAD, etc.).
653
+ connectivity : numpy.ndarray
654
+ An array of connectivity indices for the UnstructuredGrid. Should be a 1D array of integers
655
+ representing the indices of nodes that form each cell.
656
+ offsets : list or numpy.ndarray
657
+ An array of offsets for the connectivity array. Should be a 1D array of integers indicating
658
+ the start of each cell in the connectivity array. The length of this array should be equal to
659
+ the number of cells plus one (to account for the end of the last cell).
660
+ version : tuple, optional
661
+ The version of the VTKHDF format to use. Default is (2, 2).
662
+ point_data : dict, optional
663
+ A dictionary containing point data arrays to be written. Keys are the names of the data arrays,
664
+ and values are the corresponding numpy arrays.
665
+ cell_data : dict, optional
666
+ A dictionary containing cell data arrays to be written. Keys are the names of the data arrays,
667
+ and values are the corresponding numpy arrays.
668
+ field_data : dict, optional
669
+ A dictionary containing field data arrays to be written. Keys and values
670
+ should match the desired metadata structure.
671
+ additional_metadata : dict, optional
672
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
673
+ that should be stored in the file, such as attributes or custom data.
674
+ multiblock_index : int, optional
675
+ An index for the multiblock dataset, if this UnstructuredGrid is part of a larger multiblock dataset.
676
+ If not provided, defaults to None.
677
+ """
678
+ def __init__(self, filename, nodes, cell_types, connectivity, offsets, version=(2,2),
679
+ point_data=None, cell_data=None, field_data=None, additional_metadata=None, multiblock_index=None):
680
+ super().__init__(filename, additional_metadata=additional_metadata, version=version)
681
+ self.nodes = np.asarray(nodes)
682
+ self.cell_types = np.asarray(cell_types)
683
+ self.connectivity = np.asarray(connectivity)
684
+ self.offsets = self._correct_offsets(offsets, self.cell_types)
685
+ self.point_data = point_data or {}
686
+ self.cell_data = cell_data or {}
687
+ self.field_data = field_data or {}
688
+ self.multiblock_index = multiblock_index
689
+
690
+ @staticmethod
691
+ def _correct_offsets(offsets, cell_types):
692
+ """
693
+ Correct the offsets array to ensure it is in the correct format for VTKHDF.
694
+
695
+ Parameters
696
+ ----------
697
+ offsets : list or array-like
698
+ The offsets for the connectivity array. Can be a list or array of integers.
699
+ cell_types : list or array-like
700
+ The types of cells in the unstructured grid. Used to determine the number of cells.
701
+
702
+ Returns
703
+ -------
704
+ offsets : numpy.ndarray
705
+ A numpy array of offsets, corrected to ensure it starts with 0 and has the correct length.
706
+
707
+ Raises
708
+ -------
709
+ ValueError : If the offsets array is not of the correct length.
710
+ """
711
+
712
+ offsets = np.asarray(offsets)
713
+ ncells = len(cell_types)
714
+ if len(offsets) == ncells:
715
+ offsets = np.hstack([0, offsets])
716
+ elif len(offsets) != ncells + 1:
717
+ raise ValueError("Offsets must be length ncells+1 or ncells (will prepend 0 if needed).")
718
+ if offsets[0] != 0:
719
+ offsets = offsets - offsets[0]
720
+ return offsets
721
+
722
+
723
+ def write_topology(self):
724
+ """
725
+ Write the topology of the unstructured grid to the HDF5 file.
726
+
727
+ This method creates the necessary attributes and datasets in the HDF5 file to represent
728
+ the unstructured grid topology, including nodes, connectivity, offsets, cell types, and counts.
729
+
730
+ If a multiblock index is provided, it is stored as an attribute. The version and type of the dataset
731
+ are also set as attributes. The nodes, connectivity, offsets, and cell types are stored as datasets
732
+ within the root group of the HDF5 file.
733
+ """
734
+ if self.multiblock_index is not None:
735
+ self.root.attrs.create('Index', self.multiblock_index, dtype=idType)
736
+ self.root.attrs['Version'] = (2, 2)
737
+ ascii_type = 'UnstructuredGrid'.encode('ascii')
738
+ self.root.attrs.create('Type', ascii_type, dtype=h5py.string_dtype('ascii', len(ascii_type)))
739
+ self.root.create_dataset('Points', data=self.nodes, maxshape=(None, 3), dtype=fType)
740
+ self.root.create_dataset('Connectivity', data=self.connectivity, maxshape=(None,), dtype=idType)
741
+ self.root.create_dataset('Offsets', data=self.offsets, maxshape=(None,), dtype=idType)
742
+ self.root.create_dataset('Types', data=self.cell_types, maxshape=(None,), dtype=charType)
743
+ self.root.create_dataset('NumberOfPoints', data=np.array([len(self.nodes)]), dtype=idType)
744
+ self.root.create_dataset('NumberOfConnectivityIds', data=np.array([len(self.connectivity)]), dtype=idType)
745
+ self.root.create_dataset('NumberOfCells', data=np.array([len(self.cell_types)]), dtype=idType)
746
+
747
+
748
+
749
+ class VTKHDFPolyDataWriter(VTKHDFWriterBase):
750
+ """
751
+ Write PolyData datasetes to a file in VTKHDF format.
752
+
753
+ This class allows for the creation of PolyData datasets, which are unstructured grids
754
+ defined by their points, vertices, lines, polygons, and strips. It inherits from
755
+ VTKHDFWriterBase and implements the write_vtkhdf_file method to handle the specifics of writing
756
+ PolyData to a VTKHDF file.
757
+
758
+ Parameters
759
+ ----------
760
+ filename : str
761
+ The name of the file to write the PolyData to. Should end with '.vtkhdf'.
762
+ points : numpy.ndarray
763
+ An array of points defining the vertices of the PolyData. Should be of shape (N, 3),
764
+ where N is the number of points.
765
+ verts : tuple, optional
766
+ A tuple containing the connectivity and offsets for vertices in the PolyData.
767
+ Should be of the form (connectivity, offsets), where connectivity is a 1D array
768
+ of vertex indices and offsets is a 1D array indicating the start of each vertex.
769
+ lines : tuple, optional
770
+ A tuple containing the connectivity and offsets for lines in the PolyData.
771
+ Should be of the form (connectivity, offsets), where connectivity is a 1D array
772
+ of line indices and offsets is a 1D array indicating the start of each line.
773
+ polys : tuple, optional
774
+ A tuple containing the connectivity and offsets for polygons in the PolyData.
775
+ Should be of the form (connectivity, offsets), where connectivity is a 1D array
776
+ of polygon indices and offsets is a 1D array indicating the start of each polygon.
777
+ strips : tuple, optional
778
+ A tuple containing the connectivity and offsets for strips in the PolyData.
779
+ Should be of the form (connectivity, offsets), where connectivity is a 1D array
780
+ of strip indices and offsets is a 1D array indicating the start of each strip.
781
+ version : tuple, optional
782
+ The version of the VTKHDF format to use. Default is (2, 2).
783
+ point_data : dict, optional
784
+ A dictionary containing point data arrays to be written. Keys are the names of the data arrays,
785
+ and values are the corresponding numpy arrays.
786
+ cell_data : dict, optional
787
+ A dictionary containing cell data arrays to be written. Keys are the names of the data arrays,
788
+ and values are the corresponding numpy arrays.
789
+ field_data : dict, optional
790
+ A dictionary containing field data arrays to be written. Keys are the names of the data arrays,
791
+ and values are the corresponding numpy arrays.
792
+ additional_metadata : dict, optional
793
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
794
+ that should be stored in the file, such as attributes or custom data.
795
+ multiblock_index : int, optional
796
+ An index for the multiblock dataset, if this PolyData is part of a larger multiblock dataset.
797
+ If not provided, defaults to None.
798
+
799
+ """
800
+ def __init__(self, filename, points, verts=None, lines=None, polys=None, strips=None, version=(2, 2),
801
+ point_data=None, cell_data=None, field_data=None, additional_metadata=None, multiblock_index=None):
802
+ """
803
+ Initialise the VTKHDFPolyDataWriter with the necessary parameters for PolyData.
804
+
805
+ Parameters
806
+ ----------
807
+ filename : str
808
+ The name of the file to write the PolyData to. Should end with '.vtkhdf'.
809
+ points : numpy.ndarray
810
+ An array of points defining the vertices of the PolyData. Should be of shape (N, 3),
811
+ where N is the number of points.
812
+ verts : tuple, optional
813
+ A tuple containing the connectivity and offsets for vertices in the PolyData.
814
+ Should be of the form (connectivity, offsets), where connectivity is a 1D array
815
+ of vertex indices and offsets is a 1D array indicating the start of each vertex.
816
+ lines : tuple, optional
817
+ A tuple containing the connectivity and offsets for lines in the PolyData.
818
+ Should be of the form (connectivity, offsets), where connectivity is a 1D array
819
+ of line indices and offsets is a 1D array indicating the start of each line.
820
+ polys : tuple, optional
821
+ A tuple containing the connectivity and offsets for polygons in the PolyData.
822
+ Should be of the form (connectivity, offsets), where connectivity is a 1D array
823
+ of polygon indices and offsets is a 1D array indicating the start of each polygon.
824
+ strips : tuple, optional
825
+ A tuple containing the connectivity and offsets for strips in the PolyData.
826
+ Should be of the form (connectivity, offsets), where connectivity is a 1D array
827
+ of strip indices and offsets is a 1D array indicating the start of each strip.
828
+ version : tuple, optional
829
+ The version of the VTKHDF format to use. Default is (2, 2).
830
+ point_data : dict, optional
831
+ A dictionary containing point data arrays to be written. Keys are the names of the data arrays,
832
+ and values are the corresponding numpy arrays.
833
+ cell_data : dict, optional
834
+ A dictionary containing cell data arrays to be written. Keys are the names of the data arrays,
835
+ and values are the corresponding numpy arrays.
836
+ field_data : dict, optional
837
+ A dictionary containing field data arrays to be written. Keys are the names of the data arrays,
838
+ and values are the corresponding numpy arrays.
839
+ additional_metadata : dict, optional
840
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
841
+ that should be stored in the file, such as attributes or custom data.
842
+ multiblock_index : int, optional
843
+ An index for the multiblock dataset, if this PolyData is part of a larger multiblock dataset.
844
+ If not provided, defaults to None.
845
+
846
+ """
847
+
848
+ super().__init__(filename, version=version, additional_metadata=additional_metadata)
849
+
850
+ self.points = points
851
+ self.verts = verts
852
+ self.lines = lines
853
+ self.strips = strips
854
+ self.polys = polys
855
+
856
+ self.point_data = point_data
857
+ self.cell_data = cell_data
858
+ self.field_data = field_data
859
+
860
+ # set topology attributes
861
+ if self.points is not None:
862
+ self.npoints = len(points)
863
+ if self.verts is not None:
864
+ self.nverts = len(verts[1])
865
+ if self.lines is not None:
866
+ self.nlines = len(lines[1])
867
+ if self.strips is not None:
868
+ self.nstrips = len(strips[1])
869
+ if self.polys is not None:
870
+ self.npolys = len(polys[1])
871
+
872
+ if self.cell_data:
873
+ self.ncells = self.nverts + self.nlines + self.nstrips + self.npolys
874
+
875
+ self.multiblock_index = multiblock_index
876
+
877
+ # data size checks
878
+ self.check_array_sizes_for_point_data(point_data)
879
+ self.check_array_sizes_for_cell_data(cell_data)
880
+
881
+ def write_topology(self):
882
+ """
883
+ Write the topology of the PolyData to the HDF5 file.
884
+
885
+ This method creates the necessary attributes and datasets in the HDF5 file to represent
886
+ the PolyData topology, including points, vertices, lines, polygons, and strips.
887
+ If a multiblock index is provided, it is stored as an attribute. The version and type of the dataset
888
+ are also set as attributes. The points, vertices, lines, polygons, and strips are stored as datasets
889
+ within the root group of the HDF5 file.
890
+
891
+ """
892
+ if self.multiblock_index is not None:
893
+ self.root.attrs.create('Index', self.multiblock_index, dtype=idType)
894
+ self.root.attrs['Version'] = (2, 2)
895
+ ascii_type = 'PolyData'.encode('ascii')
896
+ self.root.attrs.create('Type', ascii_type, dtype=h5py.string_dtype('ascii', len(ascii_type)))
897
+ self.root.create_dataset('Points', data=self.points, maxshape=(None, 3), dtype=fType)
898
+ self.root.create_dataset('NumberOfPoints', data=np.array([len(self.points)]), dtype=idType)
899
+ for name, topo in zip(['Vertices', 'Lines', 'Polygons', 'Strips'],
900
+ [self.verts, self.lines, self.polys, self.strips]):
901
+ group = self.root.create_group(name)
902
+ if topo is not None:
903
+ connectivity, offsets = topo
904
+ if len(offsets) > 0 and offsets[0] != 0:
905
+ offsets = np.hstack([0, offsets])
906
+ group.create_dataset('Connectivity', data=connectivity, maxshape=(None,), dtype=idType)
907
+ group.create_dataset('Offsets', data=offsets, maxshape=(None,), dtype=idType)
908
+ group.create_dataset('NumberOfConnectivityIds', data=np.array([len(connectivity)]), dtype=idType)
909
+ group.create_dataset('NumberOfCells', data=np.array([len(offsets) - 1]), dtype=idType)
910
+ else:
911
+ group.create_dataset('NumberOfConnectivityIds', data=np.array([0]), maxshape=(None,), dtype=idType)
912
+ group.create_dataset('NumberOfCells', data=np.array([0]), maxshape=(None,), dtype=idType)
913
+ group.create_dataset('Offsets', data=np.array([0]), maxshape=(None,), dtype=idType)
914
+ group.create_dataset('Connectivity', (0,), maxshape=(None,), dtype=idType)
915
+
916
+
917
+
918
+
919
+ class VTKHDFRectilinearGridWriter(VTKHDFWriterBase):
920
+ """
921
+ Write RectilinearGrid dtasets to a file in VTKHDF format.
922
+
923
+ This class allows for the creation of RectilinearGrid datasets, which are structured grids
924
+ defined by their points along each axis. It inherits from VTKHDFWriterBase and implements the
925
+ write_vtkhdf_file method to handle the specifics of writing RectilinearGrid to a VTKHDF file.
926
+
927
+ Parameters
928
+ ----------
929
+ filename : str
930
+ The name of the file to write the RectilinearGrid to. Should end with '.vtkhdf'.
931
+ x_coords : numpy.ndarray
932
+ An array of x-coordinates defining the points along the x-axis.
933
+ y_coords : numpy.ndarray
934
+ An array of y-coordinates defining the points along the y-axis.
935
+ z_coords : numpy.ndarray
936
+ An array of z-coordinates defining the points along the z-axis.
937
+ version : tuple, optional
938
+ The version of the VTKHDF format to use. Default is (2, 2).
939
+ point_data : dict, optional
940
+ A dictionary containing point data arrays to be written. Keys are the names of the data arrays,
941
+ and values are the corresponding numpy arrays.
942
+ cell_data : dict, optional
943
+ A dictionary containing cell data arrays to be written. Keys are the names of the data arrays,
944
+ and values are the corresponding numpy arrays.
945
+ field_data : dict, optional
946
+ A dictionary containing field data arrays to be written. Keys are the names of the data arrays,
947
+ and values are the corresponding numpy arrays.
948
+ additional_metadata : dict, optional
949
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
950
+ that should be stored in the file, such as attributes or custom data.
951
+ multiblock_index : int, optional
952
+ An index for the multiblock dataset, if this RectilinearGrid is part of a larger multiblock dataset.
953
+ If not provided, defaults to None.
954
+
955
+ """
956
+ def __init__(self, filename, x_coords, y_coords, z_coords, version=(2, 2),
957
+ point_data=None, cell_data=None, field_data=None,
958
+ additional_metadata=None, multiblock_index=None):
959
+ """
960
+ Initialize the VTKHDFRectilinearGridWriter with the necessary parameters for RectilinearGrid.
961
+
962
+ Parameters
963
+ ----------
964
+ filename : str
965
+ The name of the file to write the RectilinearGrid to. Should end with '.vtkhdf'.
966
+ x_coords : numpy.ndarray
967
+ An array of x-coordinates defining the points along the x-axis.
968
+ y_coords : numpy.ndarray
969
+ An array of y-coordinates defining the points along the y-axis.
970
+ z_coords : numpy.ndarray
971
+ An array of z-coordinates defining the points along the z-axis.
972
+ version : tuple, optional
973
+ The version of the VTKHDF format to use. Default is (2, 2).
974
+ point_data : dict, optional
975
+ A dictionary containing point data arrays to be written. Keys are the names of the data arrays,
976
+ and values are the corresponding numpy arrays.
977
+ cell_data : dict, optional
978
+ A dictionary containing cell data arrays to be written. Keys are the names of the data arrays,
979
+ and values are the corresponding numpy arrays.
980
+ field_data : dict, optional
981
+ A dictionary containing field data arrays to be written. Keys are the names of the data arrays,
982
+ and values are the corresponding numpy arrays.
983
+ additional_metadata : dict, optional
984
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
985
+ that should be stored in the file, such as attributes or custom data.
986
+ multiblock_index : int, optional
987
+ An index for the multiblock dataset, if this RectilinearGrid is part of a larger multiblock dataset.
988
+ If not provided, defaults to None.
989
+
990
+ """
991
+ # simple check on coordinates - must first be a list or array
992
+ if not (isinstance(x_coords, (np.ndarray, list, tuple))):
993
+ raise TypeError("x must be a numpy array, tuple or list")
994
+ if not (isinstance(y_coords, (np.ndarray, list, tuple))):
995
+ raise TypeError("y must be a numpy array, tuple or list")
996
+ if not (isinstance(z_coords, (np.ndarray, list, tuple))):
997
+ raise TypeError("z must be a numpy array, tuple or list")
998
+
999
+ # check if list or array is numeric
1000
+ x = np.asarray(x_coords)
1001
+ y = np.asarray(y_coords)
1002
+ z = np.asarray(z_coords)
1003
+ if not (np.issubdtype(x.dtype, np.number)):
1004
+ raise TypeError("x must be a numeric numpy array or list")
1005
+ if not (np.issubdtype(y.dtype, np.number)):
1006
+ raise TypeError("y must be a numeric numpy array or list")
1007
+ if not (np.issubdtype(z.dtype, np.number)):
1008
+ raise TypeError("z must be a numeric numpy array or list")
1009
+
1010
+
1011
+ super().__init__(filename, additional_metadata=additional_metadata, version=version)
1012
+
1013
+ self.x = x
1014
+ self.y = y
1015
+ self.z = z
1016
+
1017
+ self.whole_extent = np.array([np.zeros(3), np.array([len(x), len(y), len(z)]) - 1]).T.flatten()
1018
+
1019
+ self.num_cells = (len(x_coords) - 1, len(y_coords) - 1, len(z_coords) - 1)
1020
+ self.num_points = (len(x_coords), len(y_coords), len(z_coords))
1021
+ self.ncells = np.prod(self.num_cells)
1022
+ self.npoints = np.prod(self.num_points)
1023
+
1024
+ self.multiblock_index = multiblock_index
1025
+
1026
+ # do check on provided data sizes before attempting to write
1027
+ self.check_array_sizes_for_cell_data(cell_data)
1028
+ self.check_array_sizes_for_point_data(point_data)
1029
+
1030
+ self.point_data = point_data
1031
+ self.cell_data = cell_data
1032
+ self.field_data = field_data
1033
+
1034
+ def write_topology(self):
1035
+ """
1036
+ Write the topology of the RectilinearGrid to the HDF5 file.
1037
+
1038
+ This method creates the necessary attributes and datasets in the HDF5 file to represent
1039
+ the RectilinearGrid topology, including x-coordinates, y-coordinates, z-coordinates,
1040
+ number of cells, and number of points. If a multiblock index is provided, it is stored as an attribute.
1041
+ The version and type of the dataset are also set as attributes. The x-coordinates, y-coordinates,
1042
+ and z-coordinates are stored as datasets within the root group of the HDF5 file.
1043
+
1044
+ Notes
1045
+ -----
1046
+ This is experimental and may not be fully functional yet. It is currently not supported by VTKHDF.
1047
+
1048
+ """
1049
+ if self.multiblock_index is not None:
1050
+ self.root.attrs.create('Index', self.multiblock_index, dtype=idType)
1051
+ self.root.attrs['Version'] = self.version
1052
+ ascii_type = 'RectilinearGrid'.encode('ascii')
1053
+ self.root.attrs.create('Type', ascii_type, dtype=h5py.string_dtype('ascii', len(ascii_type)))
1054
+
1055
+ self.root.attrs.create('WholeExtent', self.whole_extent, dtype=fType)
1056
+
1057
+
1058
+ class VTKHDFStructuredGridWriter(VTKHDFWriterBase):
1059
+ """
1060
+ Write StructuredGrid datasets to a file in VTKHDF format.
1061
+
1062
+ This class allows for the creation of StructuredGrid datasets, which are structured grids
1063
+ defined by their points along each axis. It inherits from VTKHDFWriterBase and implements the
1064
+ write_vtkhdf_file method to handle the specifics of writing StructuredGrid to a VTKHDF file.
1065
+
1066
+ Parameters
1067
+ ----------
1068
+ filename : str
1069
+ The name of the file to write the StructuredGrid to. Should end with '.vtkhdf'.
1070
+ points : numpy.ndarray
1071
+ An array of points defining the vertices of the StructuredGrid. Should be of shape (N, 3),
1072
+ where N is the number of points.
1073
+ num_cells : list or tuple
1074
+ A list or tuple defining the number of cells along each axis of the StructuredGrid.
1075
+ Should be of length 3, representing the number of cells in the x, y, and z directions.
1076
+ version : tuple, optional
1077
+ The version of the VTKHDF format to use. Default is (2, 2).
1078
+ point_data : dict, optional
1079
+ A dictionary containing point data arrays to be written. Keys are the names of the data arrays,
1080
+ and values are the corresponding numpy arrays.
1081
+ cell_data : dict, optional
1082
+ A dictionary containing cell data arrays to be written. Keys are the names of the data arrays,
1083
+ and values are the corresponding numpy arrays.
1084
+ field_data : dict, optional
1085
+ A dictionary containing field data arrays to be written. Keys are the names of the data arrays,
1086
+ and values are the corresponding numpy arrays.
1087
+ additional_metadata : dict, optional
1088
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
1089
+ that should be stored in the file, such as attributes or custom data.
1090
+ multiblock_index : int, optional
1091
+ An index for the multiblock dataset, if this StructuredGrid is part of a larger multiblock dataset.
1092
+ If not provided, defaults to None.
1093
+
1094
+ """
1095
+
1096
+ def __init__(self, filename, points, num_cells, version=(2, 2),
1097
+ point_data=None, cell_data=None, field_data=None, additional_metadata=None, multiblock_index=None):
1098
+ """
1099
+ Initialise the VTKHDFStructuredGridWriter with the necessary parameters for StructuredGrid.
1100
+
1101
+ Parameters
1102
+ ----------
1103
+ filename : str
1104
+ The name of the file to write the StructuredGrid to. Should end with '.vtkhdf'.
1105
+ points : numpy.ndarray
1106
+ An array of points defining the vertices of the StructuredGrid. Should be of shape (N, 3),
1107
+ where N is the number of points.
1108
+ version : tuple, optional
1109
+ The version of the VTKHDF format to use. Default is (2, 2).
1110
+ point_data : dict, optional
1111
+ A dictionary containing point data arrays to be written. Keys are the names of the data arrays,
1112
+ and values are the corresponding numpy arrays.
1113
+ cell_data : dict, optional
1114
+ A dictionary containing cell data arrays to be written. Keys are the names of the data arrays,
1115
+ and values are the corresponding numpy arrays.
1116
+ field_data : dict, optional
1117
+ A dictionary containing field data arrays to be written. Keys are the names of the data arrays,
1118
+ and values are the corresponding numpy arrays.
1119
+ additional_metadata : dict, optional
1120
+ Additional metadata to be written to the VTKHDF file. This can include any additional information
1121
+ that should be stored in the file, such as attributes or custom data.
1122
+ multiblock_index : int, optional
1123
+ An index for the multiblock dataset, if this StructuredGrid is part of a larger multiblock dataset.
1124
+ If not provided, defaults to None.
1125
+
1126
+ """
1127
+
1128
+ super().__init__(filename, additional_metadata=additional_metadata, version=version)
1129
+
1130
+ self.points = np.asarray(points)
1131
+
1132
+ if not (np.issubdtype(points.dtype, np.number)):
1133
+ raise TypeError("points must be a numeric numpy array")
1134
+
1135
+ if len(self.points.shape) != 2 or self.points.shape[1] != 3:
1136
+ raise ValueError("Points must be a 2D array with shape (N, 3) where N is the number of points.")
1137
+
1138
+ self.npoints = len(self.points)
1139
+
1140
+ self.num_cells = np.asarray(num_cells)
1141
+ self.num_points = self.num_cells + 1
1142
+ self.ncells = np.prod(self.num_cells)
1143
+
1144
+ self.multiblock_index = multiblock_index
1145
+
1146
+ self.whole_extent = np.array([np.zeros(3), num_cells]).T.flatten()
1147
+ self.piece_extent = self.whole_extent
1148
+
1149
+ # do check on provided data sizes before attempting to write
1150
+ self.check_array_sizes_for_point_data(point_data)
1151
+ self.check_array_sizes_for_cell_data(cell_data)
1152
+
1153
+ self.point_data = point_data
1154
+ self.cell_data = cell_data
1155
+ self.field_data = field_data
1156
+
1157
+ def write_topology(self):
1158
+ """
1159
+ Write the topology of the StructuredGrid to the HDF5 file.
1160
+
1161
+ This method creates the necessary attributes and datasets in the HDF5 file to represent
1162
+ the StructuredGrid topology, including points, number of points, and optionally
1163
+ multiblock index, version, and type. The points are stored as a dataset within the root group
1164
+ of the HDF5 file.
1165
+
1166
+ If a multiblock index is provided, it is stored as an attribute.
1167
+
1168
+ Notes
1169
+ -----
1170
+ This is experimental and may not be fully functional yet. It is currently not supported by VTKHDF.
1171
+
1172
+ """
1173
+
1174
+ if self.multiblock_index is not None:
1175
+ self.root.attrs.create('Index', self.multiblock_index, dtype=idType)
1176
+
1177
+ self.root.attrs['Version'] = self.version
1178
+ ascii_type = 'StructuredGrid'.encode('ascii')
1179
+ self.root.attrs.create('Type', ascii_type, dtype=h5py.string_dtype('ascii', len(ascii_type)))
1180
+ self.root.create_dataset('Points', data=self.points, maxshape=(None, 3), dtype=fType)
1181
+ self.root.create_dataset('NumberOfPoints', data=np.array([self.npoints]), dtype=idType)
1182
+ self.root.attrs.create('WholeExtent', self.whole_extent, dtype=fType)
1183
+ self.root.attrs.create('PieceExtent', self.piece_extent, dtype=fType)
1184
+