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/utilities.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Module for generic helper functions used in VTKio.
|
|
4
|
+
|
|
5
|
+
This module contains the following functions:
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Functions
|
|
9
|
+
---------
|
|
10
|
+
get_recursively()
|
|
11
|
+
Search a dictionary recursively.
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__author__ = 'J.P. Morrissey'
|
|
16
|
+
__copyright__ = 'Copyright 2022-2025'
|
|
17
|
+
__maintainer__ = 'J.P. Morrissey'
|
|
18
|
+
__email__ = 'morrissey.jp@gmail.com'
|
|
19
|
+
__status__ = 'Development'
|
|
20
|
+
|
|
21
|
+
# Standard Library
|
|
22
|
+
from collections.abc import MutableMapping
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_numeric_array(obj):
|
|
26
|
+
"""
|
|
27
|
+
Check if the provided object behaves like a numeric array.
|
|
28
|
+
|
|
29
|
+
This function determines if the given object has specific attributes that are generally associated with numeric
|
|
30
|
+
array-like objects. The attributes checked include addition, subtraction, multiplication, division, and power
|
|
31
|
+
operations. Objects lacking these attributes are not considered numeric arrays.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
obj : object
|
|
36
|
+
The object to check for numeric array-like behavior.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
bool
|
|
41
|
+
Returns True if the object meets the required criteria for being
|
|
42
|
+
a numeric array-like object; otherwise, it returns False.
|
|
43
|
+
"""
|
|
44
|
+
attrs = ['__add__', '__sub__', '__mul__', '__truediv__', '__pow__']
|
|
45
|
+
return all(hasattr(obj, attr) for attr in attrs)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def flatten(dictionary, parent_key='', separator='_'):
|
|
49
|
+
"""
|
|
50
|
+
Flattens a nested dictionary into a single-depth dictionary.
|
|
51
|
+
|
|
52
|
+
The function takes a nested dictionary and transforms it into a flattened dictionary
|
|
53
|
+
with a single level. Each key in the resulting dictionary is a concatenation of the
|
|
54
|
+
hierarchical keys from the original dictionary, separated by the provided separator.
|
|
55
|
+
This is particularly useful for simplifying nested structures for certain operations,
|
|
56
|
+
such as data serialization or storage.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
dictionary : dict
|
|
61
|
+
A dictionary that may contain nested dictionaries as values.
|
|
62
|
+
parent_key : str, optional
|
|
63
|
+
A string to prefix the keys of the flattened dictionary with. Defaults to an
|
|
64
|
+
empty string, meaning no prefix is added.
|
|
65
|
+
separator : str, optional
|
|
66
|
+
A string used to separate concatenated keys in the flattened dictionary. Defaults
|
|
67
|
+
to an underscore ('_').
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
dict
|
|
72
|
+
A flattened dictionary where all keys are made unique by combining their
|
|
73
|
+
hierarchical keys using the specified separator.
|
|
74
|
+
"""
|
|
75
|
+
items = []
|
|
76
|
+
for key, value in dictionary.items():
|
|
77
|
+
new_key = parent_key + separator + key if parent_key else key
|
|
78
|
+
if isinstance(value, MutableMapping):
|
|
79
|
+
items.extend(flatten(value, new_key, separator=separator).items())
|
|
80
|
+
else:
|
|
81
|
+
items.append((new_key, value))
|
|
82
|
+
return dict(items)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def dict_extract_generator(key, var):
|
|
86
|
+
"""
|
|
87
|
+
Generate values from a nested dictionary or list structure that match the specified key.
|
|
88
|
+
|
|
89
|
+
This generator function recursively traverses dictionaries and lists in search of values
|
|
90
|
+
associated with a specified key. The function yields these values when the matching key
|
|
91
|
+
is found.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
key : str
|
|
96
|
+
The key to search for in the nested structure.
|
|
97
|
+
var : dict or list
|
|
98
|
+
The nested dictionary or list structure to traverse.
|
|
99
|
+
|
|
100
|
+
Yields
|
|
101
|
+
------
|
|
102
|
+
Any
|
|
103
|
+
Values associated with the specified key in the nested structure. The type of
|
|
104
|
+
the yielded value depends on the contents of the input structure.
|
|
105
|
+
"""
|
|
106
|
+
if hasattr(var,'items'):
|
|
107
|
+
for k, v in var.items():
|
|
108
|
+
if k == key:
|
|
109
|
+
yield v
|
|
110
|
+
if isinstance(v, dict):
|
|
111
|
+
for result in dict_extract_generator(key, v):
|
|
112
|
+
yield result
|
|
113
|
+
elif isinstance(v, list):
|
|
114
|
+
for d in v:
|
|
115
|
+
for result in dict_extract_generator(key, d):
|
|
116
|
+
yield result
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_recursively(search_dict, field):
|
|
120
|
+
"""
|
|
121
|
+
Search a dictionary recursively.
|
|
122
|
+
|
|
123
|
+
Takes a dictionary with nested lists and dictionaries, and searches all dictionaries for a key of the field provided.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
search_dict : Dict [Any, Any]
|
|
128
|
+
Dictionary to be searched. It can contain nested dictionaries and lists as well as basic types such as strings, ints and floats.
|
|
129
|
+
field : key [Any]
|
|
130
|
+
Dictionary key to be searched for. Typically, a string, integer or float.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
result : List[Any]
|
|
135
|
+
The values associated with the key provided. If the key is not found, an empty list is returned.
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
Examples
|
|
139
|
+
--------
|
|
140
|
+
>>> d = {'a': 1, 'b': 2, 'c': {'da': 4, 'db': 5, 'dc': {'dda': 8, 'ddb': 9}}}
|
|
141
|
+
>>> get_recursively(d, 'da')
|
|
142
|
+
[4]
|
|
143
|
+
>>> get_recursively(d, 'ddb')
|
|
144
|
+
[9]
|
|
145
|
+
>>> get_recursively(d, 'missing_key')
|
|
146
|
+
[]
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
fields_found = []
|
|
150
|
+
|
|
151
|
+
for key, value in search_dict.items():
|
|
152
|
+
|
|
153
|
+
if key == field:
|
|
154
|
+
fields_found.append(value)
|
|
155
|
+
|
|
156
|
+
elif isinstance(value, dict):
|
|
157
|
+
results = get_recursively(value, field)
|
|
158
|
+
for result in results:
|
|
159
|
+
fields_found.append(result)
|
|
160
|
+
|
|
161
|
+
elif isinstance(value, list):
|
|
162
|
+
for item in value:
|
|
163
|
+
if isinstance(item, dict):
|
|
164
|
+
more_results = get_recursively(item, field)
|
|
165
|
+
for another_result in more_results:
|
|
166
|
+
fields_found.append(another_result)
|
|
167
|
+
|
|
168
|
+
return fields_found
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def flatten_list(nested_list):
|
|
172
|
+
"""
|
|
173
|
+
Flattens a nested list into a single list.
|
|
174
|
+
|
|
175
|
+
The function takes a nested list of arbitrary depth and recursively flattens
|
|
176
|
+
it into a single list containing all the elements. Any nested sub-lists are
|
|
177
|
+
converted into their constituent elements and appended to the resulting list.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
nested_list : list
|
|
182
|
+
A possibly nested list of elements to be flattened. The input can consist
|
|
183
|
+
of multiple levels of lists.
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
list
|
|
188
|
+
A flattened list containing all elements from the input `nested_list`,
|
|
189
|
+
including elements from any nested sub-lists.
|
|
190
|
+
"""
|
|
191
|
+
def flatten_helper(sublist, result):
|
|
192
|
+
for item in sublist:
|
|
193
|
+
if isinstance(item, list): # Improved type checking
|
|
194
|
+
flatten_helper(item, result) # Recursive call
|
|
195
|
+
else:
|
|
196
|
+
result.append(item)
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
return flatten_helper(nested_list, []) # Extracted recursive logic into helper
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def first_key(dict):
|
|
203
|
+
"""
|
|
204
|
+
Retrieve the first key from a given dictionary.
|
|
205
|
+
|
|
206
|
+
This function iterates over a dictionary and retrieves the first key it
|
|
207
|
+
encounters. If the dictionary is empty, it returns None.
|
|
208
|
+
|
|
209
|
+
Parameters
|
|
210
|
+
----------
|
|
211
|
+
dict : dict
|
|
212
|
+
The dictionary from which the first key will be retrieved.
|
|
213
|
+
|
|
214
|
+
Returns
|
|
215
|
+
-------
|
|
216
|
+
Hashable or None
|
|
217
|
+
The first key encountered in the dictionary. If the dictionary is empty,
|
|
218
|
+
returns None.
|
|
219
|
+
"""
|
|
220
|
+
for key in dict:
|
|
221
|
+
return key
|
|
222
|
+
return None
|
vtkio/version.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
This `version` module contains the version information for the VTKio package.
|
|
4
|
+
|
|
5
|
+
Created at 12:47, 23 Feb, 2025
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__author__ = 'J.P. Morrissey'
|
|
9
|
+
__copyright__ = 'Copyright 2022-2025'
|
|
10
|
+
__maintainer__ = 'J.P. Morrissey'
|
|
11
|
+
__email__ = 'morrissey.jp@gmail.com'
|
|
12
|
+
__status__ = 'Development'
|
|
13
|
+
|
|
14
|
+
# Standard Library
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Imports
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Local Sources
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = ['VERSION', 'version_info', 'version_short']
|
|
24
|
+
|
|
25
|
+
VERSION = '0.1.0.dev2'
|
|
26
|
+
"""The version of VTKio."""
|
|
27
|
+
|
|
28
|
+
def version_short() -> str:
|
|
29
|
+
"""Return the `major.minor` part of package version.
|
|
30
|
+
|
|
31
|
+
It returns '0.2' if VTKio version is '0.2.2'.
|
|
32
|
+
"""
|
|
33
|
+
return '.'.join(VERSION.split('.')[:2])
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def version_info() -> str:
|
|
37
|
+
"""Return complete version information for VTKio and its dependencies."""
|
|
38
|
+
import importlib.metadata as importlib_metadata
|
|
39
|
+
import os
|
|
40
|
+
import platform
|
|
41
|
+
import sys
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
# Local Sources
|
|
45
|
+
import _git as git
|
|
46
|
+
|
|
47
|
+
import vtkio as vtkio
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# get data about packages that are closely related to or are used by VTKio
|
|
51
|
+
package_names = {
|
|
52
|
+
'h5py',
|
|
53
|
+
'xmltodict',
|
|
54
|
+
'numpy',
|
|
55
|
+
'pybase64',
|
|
56
|
+
}
|
|
57
|
+
related_packages = []
|
|
58
|
+
|
|
59
|
+
for dist in importlib_metadata.distributions():
|
|
60
|
+
name = dist.metadata['Name']
|
|
61
|
+
if name in package_names:
|
|
62
|
+
related_packages.append(f'{name}-{dist.version}')
|
|
63
|
+
|
|
64
|
+
vtkio_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
|
65
|
+
most_recent_commit = (
|
|
66
|
+
git.git_revision(vtkio_dir) if git.is_git_repo(vtkio_dir) and git.have_git() else 'unknown'
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
info = {
|
|
70
|
+
'vtkio version': VERSION,
|
|
71
|
+
'vtkio-core build': getattr(vtkio, 'build_info', None) or vtkio.build_profile,
|
|
72
|
+
'install path': Path(__file__).resolve().parent,
|
|
73
|
+
'python version': sys.version,
|
|
74
|
+
'platform': platform.platform(),
|
|
75
|
+
'related packages': ' '.join(related_packages),
|
|
76
|
+
'commit': most_recent_commit,
|
|
77
|
+
}
|
|
78
|
+
return '\n'.join('{:>30} {}'.format(k + ':', str(v).replace('\n', ' ')) for k, v in info.items())
|
vtkio/vtk_cell_types.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
VTKWriter Class for creating VTK's XML based format.
|
|
4
|
+
|
|
5
|
+
Supports ASCII, Base64 and Appended Raw encoding of data.
|
|
6
|
+
|
|
7
|
+
Created at 13:01, 24 Feb, 2022
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__author__ = 'J.P. Morrissey'
|
|
11
|
+
__copyright__ = 'Copyright 2022-2025'
|
|
12
|
+
__maintainer__ = 'J.P. Morrissey'
|
|
13
|
+
__email__ = 'morrissey.jp@gmail.com'
|
|
14
|
+
__status__ = 'Development'
|
|
15
|
+
|
|
16
|
+
# Standard Library
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class VTK_CellType:
|
|
22
|
+
"""Generic Class for storing the various different vtk cell types."""
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
type_id: int
|
|
26
|
+
num_points: int
|
|
27
|
+
dimensionality: int
|
|
28
|
+
structure: str
|
|
29
|
+
interpolation: str
|
|
30
|
+
|
|
31
|
+
def set_num_points(self, num_points: int):
|
|
32
|
+
"""
|
|
33
|
+
Set the number of points for the cell type.
|
|
34
|
+
|
|
35
|
+
This is used for the PolyLine, Polygon and PolyVertex cell types.
|
|
36
|
+
The number of points is set to the number of points in the cell.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
num_points : int
|
|
41
|
+
The number of points in the cell.
|
|
42
|
+
"""
|
|
43
|
+
if self.name in ["PolyLine", "Polygon", "PolyVertex"]:
|
|
44
|
+
self.num_points = num_points
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError(f"Cannot set number of points for {self.name} cell type.")
|
|
47
|
+
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def set_num_triangles(self, num_triangles: int):
|
|
51
|
+
"""
|
|
52
|
+
Set the number of triangles and points for the Triangle Strip cell type.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
num_triangles : int
|
|
57
|
+
The number of triangles in the cell.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
if self.name == "TriangleStrip":
|
|
61
|
+
# The number of points in a triangle strip is the number of triangles + 2
|
|
62
|
+
self.num_points = num_triangles + 2
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError(f"Cannot set number of triangles for {self.name} cell type.")
|
|
65
|
+
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
VTK_Vertex = VTK_CellType("Vertex", 1, 1, 0, "Primary", "Linear")
|
|
70
|
+
VTK_PolyVertex = VTK_CellType("PolyVertex", 1, "n", 0, "Composite", "Linear")
|
|
71
|
+
VTK_Line = VTK_CellType("Line", 3, 2, 1, "Primary", "Linear")
|
|
72
|
+
VTK_PolyLine = VTK_CellType("PolyLine", 4, "n", 1, "Composite", "Linear")
|
|
73
|
+
VTK_Triangle = VTK_CellType("Triangle", 5, 3, 2, "Primary", "Linear")
|
|
74
|
+
VTK_TriangleStrip = VTK_CellType("TriangleStrip", 6, "n+2", 2, "Composite", "Linear")
|
|
75
|
+
VTK_Polygon = VTK_CellType("Polygon", 7, "n", 2, "Primary", "Linear")
|
|
76
|
+
VTK_Pixel = VTK_CellType("Pixel", 8, 4, 2, "Primary", "Linear")
|
|
77
|
+
VTK_Quad = VTK_CellType("Quad", 9, 4, 3, "Primary", "Linear")
|
|
78
|
+
VTK_Tetra = VTK_CellType("Tetra", 10, 4, 3, "Primary", "Linear")
|
|
79
|
+
VTK_Voxel = VTK_CellType("Voxel", 11, 8, 3, "Primary", "Linear")
|
|
80
|
+
VTK_Hexahedron = VTK_CellType("Hexahedron", 12, 8, 3, "Primary", "Linear")
|
|
81
|
+
VTK_Wedge = VTK_CellType("Wedge", 13, 6, 3, "Primary", "Linear")
|
|
82
|
+
VTK_Pyramid = VTK_CellType("Pyramid", 14, 5, 3, "Primary", "Linear")
|
|
83
|
+
VTK_Pentagonal_Prism = VTK_CellType("Pentagonal_Prism", 15, 10, 3, "Primary", "Linear")
|
|
84
|
+
VTK_Hexagonal_Prism = VTK_CellType("Hexagonal_Prism", 16, 12, 3, "Primary", "Linear")
|
|
85
|
+
VTK_Quadratic_Edge = VTK_CellType("Quadratic_Edge", 21, 3, 1, "Primary", "NonLinear")
|
|
86
|
+
VTK_Quadratic_Triangle = VTK_CellType("Quadratic_Triangle", 22, 6, 2, "Primary", "NonLinear")
|
|
87
|
+
VTK_Quadratic_Quad = VTK_CellType("Quadratic_Quad", 23, 8, 2, "Primary", "NonLinear")
|
|
88
|
+
VTK_Quadratic_Tetra = VTK_CellType("Quadratic_Tetra", 24, 10, 3, "Primary", "NonLinear")
|
|
89
|
+
VTK_Quadratic_Hexahedron = VTK_CellType("Quadratic_Hexahedron", 25, 20, 3, "Primary", "NonLinear")
|
|
90
|
+
VTK_Quadratic_Wedge = VTK_CellType("Quadratic_Wedge", 26, 12, 3, "Primary", "NonLinear")
|
|
91
|
+
VTK_Quadratic_Pyramid = VTK_CellType("Quadratic_Pyramid", 27, 13, 3, "Primary", "NonLinear")
|
|
92
|
+
VTK_BiQuadratic_Quad = VTK_CellType("BiQuadratic_Quad", 28, 9, 2, "Primary", "NonLinear")
|
|
93
|
+
VTK_TriQuadratic_Hexahedron = VTK_CellType("TriQuadratic_Hexahedron", 29, 27, 3, "Primary", "NonLinear")
|
|
94
|
+
VTK_Quadratic_Linear_Quad = VTK_CellType("Quadratic_Linear_Quad", 30, 6, 3, "Primary", "NonLinear")
|
|
95
|
+
VTK_Quadratic_Linear_Wedge = VTK_CellType("Quadratic_Linear_Wedge", 31, 12, 3, "Primary", "NonLinear")
|
|
96
|
+
VTK_BiQuadratic_Quadratic_Wedge= VTK_CellType("BiQuadratic_Quadratic_Wedge", 32, 18, 3, "Primary", "NonLinear")
|
|
97
|
+
VTK_BiQuadratic_Quadratic_Hexahedron = VTK_CellType("BiQuadratic_Quadratic_Hexahedron", 33, 24, 3, "Primary", "NonLinear")
|
|
98
|
+
VTK_BiQuadratic_Triangle = VTK_CellType("BiQuadratic_Triangle", 34, 7, 2, "Primary", "NonLinear")
|
vtkio/vtk_structures.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Module documentation goes here.
|
|
4
|
+
|
|
5
|
+
Created at 17:27, 22 Jul, 2024
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__author__ = 'J.P. Morrissey'
|
|
9
|
+
__copyright__ = 'Copyright 2022-2025'
|
|
10
|
+
__maintainer__ = 'J.P. Morrissey'
|
|
11
|
+
__email__ = 'morrissey.jp@gmail.com'
|
|
12
|
+
__status__ = 'Development'
|
|
13
|
+
|
|
14
|
+
# Standard Library
|
|
15
|
+
from dataclasses import astuple, dataclass
|
|
16
|
+
|
|
17
|
+
# Imports
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
# Local Sources
|
|
21
|
+
from .writer.writers import write_vti, write_vtp, write_vtr, write_vts, write_vtu
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def compare_exact(first, second):
|
|
25
|
+
"""Return whether two dicts of arrays are exactly equal."""
|
|
26
|
+
if first.keys() != second.keys():
|
|
27
|
+
return False
|
|
28
|
+
return all(np.array_equal(first[key], second[key]) for key in first)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def compare_approximate(first, second):
|
|
32
|
+
"""
|
|
33
|
+
Return whether two dicts of arrays are roughly equal.
|
|
34
|
+
|
|
35
|
+
This is used otherwise comparing floats would return `False`.
|
|
36
|
+
"""
|
|
37
|
+
if first.keys() != second.keys():
|
|
38
|
+
return False
|
|
39
|
+
return all(np.allclose(first[key], second[key]) for key in first)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def array_safe_eq(a, b) -> bool:
|
|
43
|
+
"""Check if a and b are equal, even if they are numpy arrays."""
|
|
44
|
+
if a is b:
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
if isinstance(a, np.ndarray) and isinstance(b, np.ndarray):
|
|
48
|
+
# return np.array_equal(a, b)
|
|
49
|
+
return np.allclose(a, b)
|
|
50
|
+
|
|
51
|
+
if isinstance(a, dict) and isinstance(b, dict):
|
|
52
|
+
return compare_approximate(a, b)
|
|
53
|
+
|
|
54
|
+
if isinstance(a, list) and isinstance(b, list):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
if isinstance(a, tuple) and isinstance(b, tuple):
|
|
58
|
+
return all((a == b).all() for a, b in zip(a, b))
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
return a == b
|
|
62
|
+
except TypeError:
|
|
63
|
+
return NotImplemented
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def dataclass_equality_check(dc1, dc2) -> bool:
|
|
67
|
+
"""Checks if two dataclasses which hold numpy arrays are equal."""
|
|
68
|
+
if dc1 is dc2:
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
if dc1.__class__ is not dc2.__class__:
|
|
72
|
+
return NotImplemented # better than False
|
|
73
|
+
|
|
74
|
+
t1 = astuple(dc1)
|
|
75
|
+
t2 = astuple(dc2)
|
|
76
|
+
|
|
77
|
+
if len(t1) != len(t2):
|
|
78
|
+
return False
|
|
79
|
+
else:
|
|
80
|
+
return all(array_safe_eq(a1, a2) for a1, a2 in zip(t1, t2))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class VTKData:
|
|
85
|
+
point_data: dict
|
|
86
|
+
cell_data: dict
|
|
87
|
+
field_data: dict
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class Cell:
|
|
92
|
+
connectivity: np.ndarray
|
|
93
|
+
offsets: np.ndarray
|
|
94
|
+
types: np.ndarray
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class PolyDataTopology:
|
|
99
|
+
connectivity: np.ndarray
|
|
100
|
+
offsets: np.ndarray
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class VTKDataArray:
|
|
105
|
+
Scalars: dict
|
|
106
|
+
Vectors: dict
|
|
107
|
+
Tensors: dict
|
|
108
|
+
Normals: dict
|
|
109
|
+
TCoords: dict
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class Grid:
|
|
114
|
+
whole_extents: np.ndarray
|
|
115
|
+
origin: np.ndarray
|
|
116
|
+
spacing: np.ndarray
|
|
117
|
+
direction: np.ndarray
|
|
118
|
+
|
|
119
|
+
def __post_init__(self):
|
|
120
|
+
_cells = np.array([self.whole_extents[1] - self.whole_extents[0],
|
|
121
|
+
self.whole_extents[3] - self.whole_extents[2],
|
|
122
|
+
self.whole_extents[5] - self.whole_extents[4]])
|
|
123
|
+
self.num_cells = np.prod(_cells)
|
|
124
|
+
self.num_points = np.prod(_cells + 1)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class GridCoordinates:
|
|
129
|
+
x: np.ndarray
|
|
130
|
+
y: np.ndarray
|
|
131
|
+
z: np.ndarray
|
|
132
|
+
whole_extents: np.ndarray
|
|
133
|
+
|
|
134
|
+
def __post_init__(self):
|
|
135
|
+
self.num_points = np.prod([self.x.size, self.y.size, self.z.size])
|
|
136
|
+
self.num_cells = np.prod([self.x.size - 1, self.y.size - 1, self.z.size - 1])
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class ImageData(VTKData):
|
|
141
|
+
grid: Grid
|
|
142
|
+
|
|
143
|
+
def __eq__(self, other):
|
|
144
|
+
return dataclass_equality_check(self, other)
|
|
145
|
+
|
|
146
|
+
def write(self, filepath, file_format='xml', xml_encoding='appended'):
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
filepath : str
|
|
152
|
+
file_format : {'xml', 'vtkhdf'}, default 'xml'
|
|
153
|
+
xml_encoding : {'ascii', 'binary', 'appended'}, default='appended'
|
|
154
|
+
Encoding of XML files can be ascii, binary or appended. The default formatting is 'appended'.
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
if file_format in ['xml','vtkhdf', 'hdf', 'hdf5', 'vtk hdf']:
|
|
158
|
+
write_vti(filepath, whole_extent=self.grid.whole_extents, piece_extent=self.grid.whole_extents,
|
|
159
|
+
spacing=self.grid.spacing, origin=self.grid.origin, direction=self.grid.direction,
|
|
160
|
+
point_data=self.point_data, cell_data=self.cell_data, field_data=self.field_data,
|
|
161
|
+
encoding=xml_encoding, file_format=file_format)
|
|
162
|
+
|
|
163
|
+
else:
|
|
164
|
+
raise NotImplementedError("This is not a supported file type")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass
|
|
168
|
+
class RectilinearData(VTKData):
|
|
169
|
+
coordinates: GridCoordinates
|
|
170
|
+
|
|
171
|
+
def __eq__(self, other):
|
|
172
|
+
return dataclass_equality_check(self, other)
|
|
173
|
+
|
|
174
|
+
def write(self, filepath, file_format='xml', xml_encoding='appended'):
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
filepath : str
|
|
180
|
+
file_format : {'xml', 'vtkhdf'}, default 'xml'
|
|
181
|
+
xml_encoding : {'ascii', 'binary', 'appended'}, default='appended'
|
|
182
|
+
Encoding of XML files can be ascii, binary or appended. The default formatting is 'appended'.
|
|
183
|
+
|
|
184
|
+
"""
|
|
185
|
+
if file_format in ['xml','vtkhdf', 'hdf', 'hdf5', 'vtk hdf']:
|
|
186
|
+
write_vtr(filepath, self.coordinates.x, self.coordinates.y, self.coordinates.z,
|
|
187
|
+
whole_extent=self.coordinates.whole_extents, piece_extent=self.coordinates.whole_extents,
|
|
188
|
+
point_data=self.point_data, cell_data=self.cell_data, field_data=self.field_data,
|
|
189
|
+
encoding=xml_encoding, file_format=file_format)
|
|
190
|
+
|
|
191
|
+
else:
|
|
192
|
+
raise NotImplementedError("This is not a supported file type")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass
|
|
196
|
+
class StructuredData(VTKData):
|
|
197
|
+
points: np.ndarray
|
|
198
|
+
whole_extents: np.ndarray
|
|
199
|
+
|
|
200
|
+
def __post_init__(self):
|
|
201
|
+
self.num_points = self.points.shape[0]
|
|
202
|
+
self.num_cells = np.prod([self.whole_extents[1] - self.whole_extents[0],
|
|
203
|
+
self.whole_extents[3] - self.whole_extents[2],
|
|
204
|
+
self.whole_extents[5] - self.whole_extents[4]])
|
|
205
|
+
|
|
206
|
+
def __eq__(self, other):
|
|
207
|
+
return dataclass_equality_check(self, other)
|
|
208
|
+
|
|
209
|
+
def write(self, filepath, file_format='xml', xml_encoding='appended'):
|
|
210
|
+
"""
|
|
211
|
+
Write the data to a file.
|
|
212
|
+
|
|
213
|
+
This function writes the data to a file in either XML or HDF5 format.
|
|
214
|
+
The file format is determined by the `file_format` parameter.
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
filepath : str
|
|
220
|
+
file_format : {'xml', 'vtkhdf'}, default 'xml'
|
|
221
|
+
xml_encoding : {'ascii', 'binary', 'appended'}, default='appended'
|
|
222
|
+
Encoding of XML files can be ascii, binary or appended. The default formatting is 'appended'.
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
if file_format in ['xml','vtkhdf', 'hdf', 'hdf5', 'vtk hdf']:
|
|
226
|
+
write_vts(filepath, self.points, whole_extent=self.whole_extents, piece_extent=self.whole_extents,
|
|
227
|
+
point_data=self.point_data, cell_data=self.cell_data, field_data=self.field_data,
|
|
228
|
+
encoding=xml_encoding, file_format=file_format)
|
|
229
|
+
|
|
230
|
+
else:
|
|
231
|
+
raise NotImplementedError("This is not a supported file type")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@dataclass
|
|
235
|
+
class UnstructuredGrid(VTKData):
|
|
236
|
+
points: np.ndarray
|
|
237
|
+
cells: Cell
|
|
238
|
+
|
|
239
|
+
def __eq__(self, other):
|
|
240
|
+
return dataclass_equality_check(self, other)
|
|
241
|
+
|
|
242
|
+
def write(self, filepath, file_format='xml', xml_encoding='appended'):
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
filepath : str
|
|
248
|
+
file_format : {'xml', 'vtkhdf'}, default 'xml'
|
|
249
|
+
xml_encoding : {'ascii', 'binary', 'appended'}, default='appended'
|
|
250
|
+
Encoding of XML files can be ascii, binary or appended. The default formatting is 'appended'.
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
if file_format in ['xml','vtkhdf', 'hdf', 'hdf5', 'vtk hdf']:
|
|
254
|
+
write_vtu(filepath, nodes=self.points, cell_type=self.cells.types, connectivity=self.cells.connectivity,
|
|
255
|
+
offsets=self.cells.offsets, point_data=self.point_data, cell_data=self.cell_data,
|
|
256
|
+
field_data=self.field_data, encoding=xml_encoding, file_format=file_format)
|
|
257
|
+
|
|
258
|
+
else:
|
|
259
|
+
raise NotImplementedError("This is not a supported file type")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass
|
|
263
|
+
class PolyData(VTKData):
|
|
264
|
+
points: np.ndarray
|
|
265
|
+
verts: PolyDataTopology
|
|
266
|
+
lines: PolyDataTopology
|
|
267
|
+
strips: PolyDataTopology
|
|
268
|
+
polys: PolyDataTopology
|
|
269
|
+
|
|
270
|
+
def __eq__(self, other):
|
|
271
|
+
return dataclass_equality_check(self, other)
|
|
272
|
+
|
|
273
|
+
def write(self, filepath, file_format='xml', xml_encoding='appended'):
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
filepath : str
|
|
279
|
+
file_format : {'xml', 'vtkhdf'}, default 'xml'
|
|
280
|
+
xml_encoding : {'ascii', 'binary', 'appended'}, default='appended'
|
|
281
|
+
Encoding of XML files can be ascii, binary or appended. The default formatting is 'appended'.
|
|
282
|
+
|
|
283
|
+
"""
|
|
284
|
+
lines = None
|
|
285
|
+
verts = None
|
|
286
|
+
strips = None
|
|
287
|
+
polys = None
|
|
288
|
+
|
|
289
|
+
# check for none
|
|
290
|
+
if self.lines:
|
|
291
|
+
lines = astuple(self.lines)
|
|
292
|
+
if self.strips:
|
|
293
|
+
strips = astuple(self.strips)
|
|
294
|
+
if self.polys:
|
|
295
|
+
polys = astuple(self.polys)
|
|
296
|
+
if self.verts:
|
|
297
|
+
verts = astuple(self.verts)
|
|
298
|
+
|
|
299
|
+
if file_format in ['xml','vtkhdf', 'hdf', 'hdf5', 'vtk hdf']:
|
|
300
|
+
|
|
301
|
+
write_vtp(filepath, points=self.points, verts=verts, lines=lines, strips=strips, polys=polys,
|
|
302
|
+
point_data=self.point_data, cell_data=self.cell_data, field_data=self.field_data,
|
|
303
|
+
encoding=xml_encoding, file_format=file_format,)
|
|
304
|
+
|
|
305
|
+
else:
|
|
306
|
+
raise NotImplementedError("This is not a supported file type")
|