subsurface-terra 2025.1.0rc15__py3-none-any.whl → 2025.1.0rc17__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.
- subsurface/__init__.py +31 -31
- subsurface/_version.py +34 -21
- subsurface/api/__init__.py +13 -13
- subsurface/api/interfaces/__init__.py +3 -3
- subsurface/api/interfaces/stream.py +136 -136
- subsurface/api/reader/read_wells.py +78 -78
- subsurface/core/geological_formats/boreholes/_combine_trajectories.py +117 -117
- subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py +236 -234
- subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py +163 -163
- subsurface/core/geological_formats/boreholes/boreholes.py +140 -140
- subsurface/core/geological_formats/boreholes/collars.py +26 -26
- subsurface/core/geological_formats/boreholes/survey.py +86 -86
- subsurface/core/geological_formats/fault.py +47 -47
- subsurface/core/reader_helpers/reader_unstruct.py +11 -11
- subsurface/core/reader_helpers/readers_data.py +130 -130
- subsurface/core/reader_helpers/readers_wells.py +13 -13
- subsurface/core/structs/__init__.py +3 -3
- subsurface/core/structs/base_structures/__init__.py +2 -2
- subsurface/core/structs/base_structures/_aux.py +69 -0
- subsurface/core/structs/base_structures/_liquid_earth_mesh.py +121 -121
- subsurface/core/structs/base_structures/_unstructured_data_constructor.py +70 -70
- subsurface/core/structs/base_structures/base_structures_enum.py +6 -6
- subsurface/core/structs/base_structures/structured_data.py +282 -282
- subsurface/core/structs/base_structures/unstructured_data.py +338 -319
- subsurface/core/structs/structured_elements/octree_mesh.py +10 -10
- subsurface/core/structs/structured_elements/structured_grid.py +59 -59
- subsurface/core/structs/structured_elements/structured_mesh.py +9 -9
- subsurface/core/structs/unstructured_elements/__init__.py +3 -3
- subsurface/core/structs/unstructured_elements/line_set.py +72 -72
- subsurface/core/structs/unstructured_elements/point_set.py +43 -43
- subsurface/core/structs/unstructured_elements/tetrahedron_mesh.py +35 -35
- subsurface/core/structs/unstructured_elements/triangular_surface.py +62 -62
- subsurface/core/utils/utils_core.py +38 -38
- subsurface/modules/reader/__init__.py +13 -13
- subsurface/modules/reader/faults/faults.py +80 -80
- subsurface/modules/reader/from_binary.py +46 -46
- subsurface/modules/reader/mesh/_GOCAD_mesh.py +82 -82
- subsurface/modules/reader/mesh/_trimesh_reader.py +447 -447
- subsurface/modules/reader/mesh/csv_mesh_reader.py +53 -53
- subsurface/modules/reader/mesh/dxf_reader.py +177 -177
- subsurface/modules/reader/mesh/glb_reader.py +30 -30
- subsurface/modules/reader/mesh/mx_reader.py +232 -232
- subsurface/modules/reader/mesh/obj_reader.py +53 -53
- subsurface/modules/reader/mesh/omf_mesh_reader.py +43 -43
- subsurface/modules/reader/mesh/surface_reader.py +56 -56
- subsurface/modules/reader/mesh/surfaces_api.py +41 -41
- subsurface/modules/reader/profiles/__init__.py +3 -3
- subsurface/modules/reader/profiles/profiles_core.py +197 -197
- subsurface/modules/reader/read_netcdf.py +38 -38
- subsurface/modules/reader/topography/__init__.py +7 -7
- subsurface/modules/reader/topography/topo_core.py +100 -100
- subsurface/modules/reader/volume/read_grav3d.py +447 -428
- subsurface/modules/reader/volume/read_volume.py +327 -230
- subsurface/modules/reader/volume/segy_reader.py +105 -105
- subsurface/modules/reader/volume/seismic.py +173 -173
- subsurface/modules/reader/volume/volume_utils.py +43 -43
- subsurface/modules/reader/wells/DEP/__init__.py +43 -43
- subsurface/modules/reader/wells/DEP/_well_files_reader.py +167 -167
- subsurface/modules/reader/wells/DEP/_wells_api.py +61 -61
- subsurface/modules/reader/wells/DEP/_welly_reader.py +180 -180
- subsurface/modules/reader/wells/DEP/pandas_to_welly.py +212 -212
- subsurface/modules/reader/wells/_read_to_df.py +57 -57
- subsurface/modules/reader/wells/read_borehole_interface.py +148 -148
- subsurface/modules/reader/wells/wells_utils.py +68 -68
- subsurface/modules/tools/mocking_aux.py +104 -104
- subsurface/modules/visualization/__init__.py +2 -2
- subsurface/modules/visualization/to_pyvista.py +320 -320
- subsurface/modules/writer/to_binary.py +12 -12
- subsurface/modules/writer/to_rex/common.py +78 -78
- subsurface/modules/writer/to_rex/data_struct.py +74 -74
- subsurface/modules/writer/to_rex/gempy_to_rexfile.py +791 -791
- subsurface/modules/writer/to_rex/material_encoder.py +44 -44
- subsurface/modules/writer/to_rex/mesh_encoder.py +152 -152
- subsurface/modules/writer/to_rex/to_rex.py +115 -115
- subsurface/modules/writer/to_rex/utils.py +15 -15
- subsurface/optional_requirements.py +116 -116
- {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/METADATA +194 -194
- subsurface_terra-2025.1.0rc17.dist-info/RECORD +99 -0
- {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/WHEEL +1 -1
- {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/licenses/LICENSE +203 -203
- subsurface_terra-2025.1.0rc15.dist-info/RECORD +0 -98
- {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc17.dist-info}/top_level.txt +0 -0
|
@@ -1,320 +1,320 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
|
|
3
|
-
import warnings
|
|
4
|
-
|
|
5
|
-
from typing import Union, Tuple, Optional
|
|
6
|
-
|
|
7
|
-
from ... import optional_requirements
|
|
8
|
-
from ...core.structs.unstructured_elements import PointSet, TriSurf, LineSet, TetraMesh
|
|
9
|
-
from ...core.structs.structured_elements.structured_grid import StructuredGrid
|
|
10
|
-
import numpy as np
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
import pyvista as pv
|
|
14
|
-
except ImportError:
|
|
15
|
-
warnings.warn('Pyvista is not installed. Some visualization functions will not work.')
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def pv_plot(meshes: list,
|
|
19
|
-
image_2d=False,
|
|
20
|
-
ve=None,
|
|
21
|
-
cmap='viridis',
|
|
22
|
-
plotter_kwargs: dict = None,
|
|
23
|
-
add_mesh_kwargs: dict = None,
|
|
24
|
-
background_plotter=False):
|
|
25
|
-
"""Function to plot meshes in vtk using pyvista
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
meshes (List[pv.PolyData]):
|
|
29
|
-
image_2d (bool): If True convert plot to matplotlib imshow. This helps for visualizing
|
|
30
|
-
the plot in IDEs
|
|
31
|
-
ve (float): vertical exaggeration
|
|
32
|
-
plotter_kwargs (dict): pyvista.Plotter kwargs
|
|
33
|
-
add_mesh_kwargs (dict): pyvista.add_mesh kwargs
|
|
34
|
-
background_plotter (bool): if true and pyvistaqt installed use pyvista
|
|
35
|
-
backgroung plotter.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
add_mesh_kwargs = dict() if add_mesh_kwargs is None else add_mesh_kwargs
|
|
39
|
-
p: pv.Pll = init_plotter(image_2d, ve, plotter_kwargs)
|
|
40
|
-
|
|
41
|
-
for m in meshes:
|
|
42
|
-
# Check if m has texture data
|
|
43
|
-
texture = None
|
|
44
|
-
if hasattr(m, '_textures') and isinstance(m._textures, dict):
|
|
45
|
-
texture = m._textures.get(0, None)
|
|
46
|
-
|
|
47
|
-
p.add_mesh(
|
|
48
|
-
mesh=m,
|
|
49
|
-
cmap=cmap,
|
|
50
|
-
categories=True,
|
|
51
|
-
texture=texture,
|
|
52
|
-
**add_mesh_kwargs
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
p.show_bounds()
|
|
56
|
-
|
|
57
|
-
if image_2d is False:
|
|
58
|
-
p.show()
|
|
59
|
-
return p
|
|
60
|
-
else:
|
|
61
|
-
fig = pyvista_to_matplotlib(p)
|
|
62
|
-
return fig
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def pyvista_to_matplotlib(p: "pv.Plotter"):
|
|
66
|
-
try:
|
|
67
|
-
import matplotlib.pyplot as plt
|
|
68
|
-
except ImportError:
|
|
69
|
-
raise ImportError('Matplotlib is necessary for generating a 2D image.')
|
|
70
|
-
img = p.show(screenshot=True)
|
|
71
|
-
img = p.last_image
|
|
72
|
-
fig = plt.imshow(img)
|
|
73
|
-
plt.axis('off')
|
|
74
|
-
plt.show(block=False)
|
|
75
|
-
p.close()
|
|
76
|
-
return fig
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def init_plotter(
|
|
80
|
-
image_2d=False,
|
|
81
|
-
ve=None,
|
|
82
|
-
plotter_kwargs: dict = None
|
|
83
|
-
) -> "pv.Plotter":
|
|
84
|
-
plotter_kwargs = dict() if plotter_kwargs is None else plotter_kwargs
|
|
85
|
-
off_screen = True if image_2d is True else None
|
|
86
|
-
p = pv.Plotter(**plotter_kwargs, off_screen=off_screen)
|
|
87
|
-
if ve is not None:
|
|
88
|
-
p.set_scale(zscale=ve)
|
|
89
|
-
return p
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def to_pyvista_points(point_set: PointSet):
|
|
93
|
-
"""Create pyvista.PolyData from PointSet
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
point_set (PointSet): Class for pointset based data structures.
|
|
97
|
-
|
|
98
|
-
Returns:
|
|
99
|
-
pv.PolyData
|
|
100
|
-
"""
|
|
101
|
-
pv = optional_requirements.require_pyvista()
|
|
102
|
-
poly: pv.PolyData = pv.PolyData(point_set.data.vertex)
|
|
103
|
-
poly.point_data.update(point_set.data.attributes_to_dict)
|
|
104
|
-
|
|
105
|
-
return poly
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def to_pyvista_mesh(triangular_surface: TriSurf) -> "pv.PolyData":
|
|
109
|
-
"""Create planar surface PolyData from unstructured element such as TriSurf
|
|
110
|
-
|
|
111
|
-
Returns:
|
|
112
|
-
mesh texture
|
|
113
|
-
"""
|
|
114
|
-
nve = triangular_surface.mesh.n_vertex_per_element
|
|
115
|
-
vertices = triangular_surface.mesh.vertex
|
|
116
|
-
|
|
117
|
-
# ? We need better name for these variables
|
|
118
|
-
num_vertex_elements = np.full(triangular_surface.mesh.n_elements, nve)
|
|
119
|
-
x = triangular_surface.mesh.cells
|
|
120
|
-
|
|
121
|
-
cells = np.c_[num_vertex_elements, x]
|
|
122
|
-
|
|
123
|
-
pv = optional_requirements.require_pyvista()
|
|
124
|
-
mesh = pv.PolyData(vertices, cells)
|
|
125
|
-
mesh.cell_data.update(triangular_surface.mesh.attributes_to_dict)
|
|
126
|
-
mesh.point_data.update(triangular_surface.mesh.points_attributes)
|
|
127
|
-
|
|
128
|
-
# If UV coordinates exist in points_attributes, set them as texture coordinates
|
|
129
|
-
if triangular_surface.has_texture_data_with_uv:
|
|
130
|
-
uv = np.column_stack((
|
|
131
|
-
triangular_surface.mesh.points_attributes['u'],
|
|
132
|
-
triangular_surface.mesh.points_attributes['v']
|
|
133
|
-
))
|
|
134
|
-
mesh.active_texture_coordinates = uv
|
|
135
|
-
if triangular_surface.texture is not None:
|
|
136
|
-
texture_data = np.asarray(triangular_surface.texture.values, dtype=np.float32)
|
|
137
|
-
mesh._textures = {0: texture_data}
|
|
138
|
-
mesh.active_scalars_name = None
|
|
139
|
-
|
|
140
|
-
elif triangular_surface.has_texture_data_without_uv:
|
|
141
|
-
mesh.texture_map_to_plane(
|
|
142
|
-
inplace=True,
|
|
143
|
-
origin=triangular_surface.texture_origin,
|
|
144
|
-
point_u=triangular_surface.texture_point_u,
|
|
145
|
-
point_v=triangular_surface.texture_point_v
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
texture_data = np.asarray(triangular_surface.texture.values, dtype=np.float32)
|
|
149
|
-
mesh._textures = {0: texture_data}
|
|
150
|
-
mesh.active_scalars_name = None
|
|
151
|
-
|
|
152
|
-
return mesh
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def to_pyvista_mesh_and_texture(triangular_surface: Union[TriSurf], ) -> Tuple["pv.PolyData", Optional[np.array]]:
|
|
156
|
-
"""Create planar surface PolyData from unstructured element such as TriSurf
|
|
157
|
-
|
|
158
|
-
Returns:
|
|
159
|
-
mesh texture
|
|
160
|
-
"""
|
|
161
|
-
mesh = to_pyvista_mesh(triangular_surface)
|
|
162
|
-
|
|
163
|
-
if triangular_surface.texture is None:
|
|
164
|
-
raise ValueError('unstructured_element needs texture data to be mapped.')
|
|
165
|
-
|
|
166
|
-
mesh.texture_map_to_plane(
|
|
167
|
-
inplace=True,
|
|
168
|
-
origin=triangular_surface.texture_origin,
|
|
169
|
-
point_u=triangular_surface.texture_point_u,
|
|
170
|
-
point_v=triangular_surface.texture_point_v
|
|
171
|
-
)
|
|
172
|
-
tex = pv.numpy_to_texture(triangular_surface.texture.values)
|
|
173
|
-
mesh._textures = {0: tex}
|
|
174
|
-
|
|
175
|
-
from vtkmodules.util.numpy_support import vtk_to_numpy
|
|
176
|
-
uv = vtk_to_numpy(mesh.GetPointData().GetTCoords())
|
|
177
|
-
return mesh, uv
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class PyvistaScalarType(enum.Enum):
|
|
181
|
-
POINT = 'point'
|
|
182
|
-
CELL = 'cell'
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def to_pyvista_line(
|
|
186
|
-
line_set: LineSet,
|
|
187
|
-
as_tube=True,
|
|
188
|
-
radius=None,
|
|
189
|
-
spline=False,
|
|
190
|
-
n_interp_points=1000,
|
|
191
|
-
scalar_type: PyvistaScalarType = PyvistaScalarType.POINT,
|
|
192
|
-
active_scalar: Optional[str] = None
|
|
193
|
-
):
|
|
194
|
-
nve = line_set.data.n_vertex_per_element
|
|
195
|
-
vertices = line_set.data.vertex
|
|
196
|
-
cells = np.c_[np.full(line_set.data.n_elements, nve),
|
|
197
|
-
line_set.data.cells]
|
|
198
|
-
if spline is False:
|
|
199
|
-
mesh = pv.PolyData()
|
|
200
|
-
mesh.points = vertices
|
|
201
|
-
mesh.lines = cells
|
|
202
|
-
else:
|
|
203
|
-
raise NotImplementedError
|
|
204
|
-
|
|
205
|
-
match scalar_type:
|
|
206
|
-
case PyvistaScalarType.POINT:
|
|
207
|
-
mesh.point_data.update(line_set.data.points_attributes_to_dict)
|
|
208
|
-
if active_scalar is not None:
|
|
209
|
-
mesh.set_active_scalars(active_scalar, preference='point')
|
|
210
|
-
case PyvistaScalarType.CELL:
|
|
211
|
-
mesh.cell_data.update(line_set.data.attributes_to_dict)
|
|
212
|
-
if active_scalar is not None:
|
|
213
|
-
mesh.set_active_scalars(active_scalar, preference='cell')
|
|
214
|
-
if as_tube is True:
|
|
215
|
-
return mesh.tube(radius=radius)
|
|
216
|
-
else:
|
|
217
|
-
return mesh
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
def to_pyvista_tetra(tetra_mesh: TetraMesh):
|
|
221
|
-
"""Create pyvista.UnstructuredGrid"""
|
|
222
|
-
vertices = tetra_mesh.data.vertex
|
|
223
|
-
tets = tetra_mesh.data.cells
|
|
224
|
-
cells = np.c_[np.full(len(tets), 4), tets]
|
|
225
|
-
import vtk
|
|
226
|
-
ctypes = np.array([vtk.VTK_TETRA, ], np.int32)
|
|
227
|
-
mesh = pv.UnstructuredGrid(cells, ctypes, vertices)
|
|
228
|
-
mesh.cell_data.update(tetra_mesh.data.attributes_to_dict)
|
|
229
|
-
return mesh
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def to_pyvista_grid(
|
|
233
|
-
structured_grid: StructuredGrid,
|
|
234
|
-
data_set_name: str = None,
|
|
235
|
-
attribute_slice: dict = None,
|
|
236
|
-
data_order: str = 'F'
|
|
237
|
-
) -> "pyvista.StructuredGrid":
|
|
238
|
-
"""
|
|
239
|
-
|
|
240
|
-
Args:
|
|
241
|
-
structured_grid:
|
|
242
|
-
data_set_name:
|
|
243
|
-
attribute_slice: dictionary to select which 3D array will be displayed as color
|
|
244
|
-
|
|
245
|
-
Returns:
|
|
246
|
-
|
|
247
|
-
"""
|
|
248
|
-
if attribute_slice is None:
|
|
249
|
-
attribute_slice = dict()
|
|
250
|
-
|
|
251
|
-
if data_set_name is None:
|
|
252
|
-
data_set_name = structured_grid.ds.active_data_array_name
|
|
253
|
-
|
|
254
|
-
cart_dims = structured_grid.cartesian_dimensions
|
|
255
|
-
data_dims = structured_grid.ds.data[data_set_name].sel(**attribute_slice).ndim
|
|
256
|
-
if cart_dims < data_dims:
|
|
257
|
-
raise AttributeError('Data dimension and cartesian dimensions must match.'
|
|
258
|
-
'Possibly there are not valid dimension name in the'
|
|
259
|
-
'xarray.DataArray. These are X Y Z x y z')
|
|
260
|
-
|
|
261
|
-
if data_dims == 2:
|
|
262
|
-
meshgrid = structured_grid.meshgrid_2d(data_set_name)
|
|
263
|
-
elif data_dims == 3:
|
|
264
|
-
meshgrid = structured_grid.meshgrid_3d
|
|
265
|
-
else:
|
|
266
|
-
raise AttributeError('The DataArray does not have valid dimensionality. '
|
|
267
|
-
'Possibly there are not valid dimension name in the'
|
|
268
|
-
'xarray.DataArray. These are X Y Z x y z')
|
|
269
|
-
|
|
270
|
-
pv = optional_requirements.require_pyvista()
|
|
271
|
-
mesh = pv.StructuredGrid(*meshgrid)
|
|
272
|
-
update_grid_attribute(mesh, structured_grid, data_order,
|
|
273
|
-
attribute_slice, data_set_name)
|
|
274
|
-
|
|
275
|
-
return mesh
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def update_grid_attribute(
|
|
279
|
-
mesh: 'pv.StructuredGrid',
|
|
280
|
-
structured_grid: StructuredGrid,
|
|
281
|
-
data_order='F',
|
|
282
|
-
attribute_slice=None,
|
|
283
|
-
data_set_name=None
|
|
284
|
-
):
|
|
285
|
-
if attribute_slice is None:
|
|
286
|
-
attribute_slice = dict()
|
|
287
|
-
|
|
288
|
-
if data_set_name is None:
|
|
289
|
-
data_set_name = structured_grid.ds.active_data_array_name
|
|
290
|
-
import xarray as xr
|
|
291
|
-
dataset: xr.DataArray = structured_grid.ds.data[data_set_name]
|
|
292
|
-
|
|
293
|
-
attributeData = {data_set_name: dataset.sel(**attribute_slice).values.ravel(data_order)}
|
|
294
|
-
mesh.point_data.update(attributeData)
|
|
295
|
-
|
|
296
|
-
return mesh
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def _n_cartesian_coord(attribute, structured_grid):
|
|
300
|
-
coord_names = np.array(['X', 'Y', 'Z', 'x', 'y', 'z'])
|
|
301
|
-
ndim = np.isin(coord_names, structured_grid.ds.data[attribute].dims).sum()
|
|
302
|
-
return ndim
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
def _generate_colors_from_colormap(num_colors, cmap_name='viridis'):
|
|
306
|
-
"""
|
|
307
|
-
Generate a sequence of colors from a given Matplotlib colormap.
|
|
308
|
-
|
|
309
|
-
Parameters:
|
|
310
|
-
num_colors (int): Number of colors to generate.
|
|
311
|
-
cmap_name (str): Name of the Matplotlib colormap to use.
|
|
312
|
-
|
|
313
|
-
Returns:
|
|
314
|
-
list of tuple: List of RGB color tuples.
|
|
315
|
-
"""
|
|
316
|
-
import matplotlib.pyplot as plt
|
|
317
|
-
colormap = plt.cm.get_cmap(cmap_name)
|
|
318
|
-
colors = colormap(np.linspace(0, 1, num_colors))
|
|
319
|
-
# Convert from RGBA to RGB and scale to 0-255
|
|
320
|
-
return [(int(r * 255), int(g * 255), int(b * 255)) for r, g, b, _ in colors]
|
|
1
|
+
import enum
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
from typing import Union, Tuple, Optional
|
|
6
|
+
|
|
7
|
+
from ... import optional_requirements
|
|
8
|
+
from ...core.structs.unstructured_elements import PointSet, TriSurf, LineSet, TetraMesh
|
|
9
|
+
from ...core.structs.structured_elements.structured_grid import StructuredGrid
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import pyvista as pv
|
|
14
|
+
except ImportError:
|
|
15
|
+
warnings.warn('Pyvista is not installed. Some visualization functions will not work.')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def pv_plot(meshes: list,
|
|
19
|
+
image_2d=False,
|
|
20
|
+
ve=None,
|
|
21
|
+
cmap='viridis',
|
|
22
|
+
plotter_kwargs: dict = None,
|
|
23
|
+
add_mesh_kwargs: dict = None,
|
|
24
|
+
background_plotter=False):
|
|
25
|
+
"""Function to plot meshes in vtk using pyvista
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
meshes (List[pv.PolyData]):
|
|
29
|
+
image_2d (bool): If True convert plot to matplotlib imshow. This helps for visualizing
|
|
30
|
+
the plot in IDEs
|
|
31
|
+
ve (float): vertical exaggeration
|
|
32
|
+
plotter_kwargs (dict): pyvista.Plotter kwargs
|
|
33
|
+
add_mesh_kwargs (dict): pyvista.add_mesh kwargs
|
|
34
|
+
background_plotter (bool): if true and pyvistaqt installed use pyvista
|
|
35
|
+
backgroung plotter.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
add_mesh_kwargs = dict() if add_mesh_kwargs is None else add_mesh_kwargs
|
|
39
|
+
p: pv.Pll = init_plotter(image_2d, ve, plotter_kwargs)
|
|
40
|
+
|
|
41
|
+
for m in meshes:
|
|
42
|
+
# Check if m has texture data
|
|
43
|
+
texture = None
|
|
44
|
+
if hasattr(m, '_textures') and isinstance(m._textures, dict):
|
|
45
|
+
texture = m._textures.get(0, None)
|
|
46
|
+
|
|
47
|
+
p.add_mesh(
|
|
48
|
+
mesh=m,
|
|
49
|
+
cmap=cmap,
|
|
50
|
+
categories=True,
|
|
51
|
+
texture=texture,
|
|
52
|
+
**add_mesh_kwargs
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
p.show_bounds()
|
|
56
|
+
|
|
57
|
+
if image_2d is False:
|
|
58
|
+
p.show()
|
|
59
|
+
return p
|
|
60
|
+
else:
|
|
61
|
+
fig = pyvista_to_matplotlib(p)
|
|
62
|
+
return fig
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def pyvista_to_matplotlib(p: "pv.Plotter"):
|
|
66
|
+
try:
|
|
67
|
+
import matplotlib.pyplot as plt
|
|
68
|
+
except ImportError:
|
|
69
|
+
raise ImportError('Matplotlib is necessary for generating a 2D image.')
|
|
70
|
+
img = p.show(screenshot=True)
|
|
71
|
+
img = p.last_image
|
|
72
|
+
fig = plt.imshow(img)
|
|
73
|
+
plt.axis('off')
|
|
74
|
+
plt.show(block=False)
|
|
75
|
+
p.close()
|
|
76
|
+
return fig
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def init_plotter(
|
|
80
|
+
image_2d=False,
|
|
81
|
+
ve=None,
|
|
82
|
+
plotter_kwargs: dict = None
|
|
83
|
+
) -> "pv.Plotter":
|
|
84
|
+
plotter_kwargs = dict() if plotter_kwargs is None else plotter_kwargs
|
|
85
|
+
off_screen = True if image_2d is True else None
|
|
86
|
+
p = pv.Plotter(**plotter_kwargs, off_screen=off_screen)
|
|
87
|
+
if ve is not None:
|
|
88
|
+
p.set_scale(zscale=ve)
|
|
89
|
+
return p
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def to_pyvista_points(point_set: PointSet):
|
|
93
|
+
"""Create pyvista.PolyData from PointSet
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
point_set (PointSet): Class for pointset based data structures.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
pv.PolyData
|
|
100
|
+
"""
|
|
101
|
+
pv = optional_requirements.require_pyvista()
|
|
102
|
+
poly: pv.PolyData = pv.PolyData(point_set.data.vertex)
|
|
103
|
+
poly.point_data.update(point_set.data.attributes_to_dict)
|
|
104
|
+
|
|
105
|
+
return poly
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def to_pyvista_mesh(triangular_surface: TriSurf) -> "pv.PolyData":
|
|
109
|
+
"""Create planar surface PolyData from unstructured element such as TriSurf
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
mesh texture
|
|
113
|
+
"""
|
|
114
|
+
nve = triangular_surface.mesh.n_vertex_per_element
|
|
115
|
+
vertices = triangular_surface.mesh.vertex
|
|
116
|
+
|
|
117
|
+
# ? We need better name for these variables
|
|
118
|
+
num_vertex_elements = np.full(triangular_surface.mesh.n_elements, nve)
|
|
119
|
+
x = triangular_surface.mesh.cells
|
|
120
|
+
|
|
121
|
+
cells = np.c_[num_vertex_elements, x]
|
|
122
|
+
|
|
123
|
+
pv = optional_requirements.require_pyvista()
|
|
124
|
+
mesh = pv.PolyData(vertices, cells)
|
|
125
|
+
mesh.cell_data.update(triangular_surface.mesh.attributes_to_dict)
|
|
126
|
+
mesh.point_data.update(triangular_surface.mesh.points_attributes)
|
|
127
|
+
|
|
128
|
+
# If UV coordinates exist in points_attributes, set them as texture coordinates
|
|
129
|
+
if triangular_surface.has_texture_data_with_uv:
|
|
130
|
+
uv = np.column_stack((
|
|
131
|
+
triangular_surface.mesh.points_attributes['u'],
|
|
132
|
+
triangular_surface.mesh.points_attributes['v']
|
|
133
|
+
))
|
|
134
|
+
mesh.active_texture_coordinates = uv
|
|
135
|
+
if triangular_surface.texture is not None:
|
|
136
|
+
texture_data = np.asarray(triangular_surface.texture.values, dtype=np.float32)
|
|
137
|
+
mesh._textures = {0: texture_data}
|
|
138
|
+
mesh.active_scalars_name = None
|
|
139
|
+
|
|
140
|
+
elif triangular_surface.has_texture_data_without_uv:
|
|
141
|
+
mesh.texture_map_to_plane(
|
|
142
|
+
inplace=True,
|
|
143
|
+
origin=triangular_surface.texture_origin,
|
|
144
|
+
point_u=triangular_surface.texture_point_u,
|
|
145
|
+
point_v=triangular_surface.texture_point_v
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
texture_data = np.asarray(triangular_surface.texture.values, dtype=np.float32)
|
|
149
|
+
mesh._textures = {0: texture_data}
|
|
150
|
+
mesh.active_scalars_name = None
|
|
151
|
+
|
|
152
|
+
return mesh
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def to_pyvista_mesh_and_texture(triangular_surface: Union[TriSurf], ) -> Tuple["pv.PolyData", Optional[np.array]]:
|
|
156
|
+
"""Create planar surface PolyData from unstructured element such as TriSurf
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
mesh texture
|
|
160
|
+
"""
|
|
161
|
+
mesh = to_pyvista_mesh(triangular_surface)
|
|
162
|
+
|
|
163
|
+
if triangular_surface.texture is None:
|
|
164
|
+
raise ValueError('unstructured_element needs texture data to be mapped.')
|
|
165
|
+
|
|
166
|
+
mesh.texture_map_to_plane(
|
|
167
|
+
inplace=True,
|
|
168
|
+
origin=triangular_surface.texture_origin,
|
|
169
|
+
point_u=triangular_surface.texture_point_u,
|
|
170
|
+
point_v=triangular_surface.texture_point_v
|
|
171
|
+
)
|
|
172
|
+
tex = pv.numpy_to_texture(triangular_surface.texture.values)
|
|
173
|
+
mesh._textures = {0: tex}
|
|
174
|
+
|
|
175
|
+
from vtkmodules.util.numpy_support import vtk_to_numpy
|
|
176
|
+
uv = vtk_to_numpy(mesh.GetPointData().GetTCoords())
|
|
177
|
+
return mesh, uv
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class PyvistaScalarType(enum.Enum):
|
|
181
|
+
POINT = 'point'
|
|
182
|
+
CELL = 'cell'
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def to_pyvista_line(
|
|
186
|
+
line_set: LineSet,
|
|
187
|
+
as_tube=True,
|
|
188
|
+
radius=None,
|
|
189
|
+
spline=False,
|
|
190
|
+
n_interp_points=1000,
|
|
191
|
+
scalar_type: PyvistaScalarType = PyvistaScalarType.POINT,
|
|
192
|
+
active_scalar: Optional[str] = None
|
|
193
|
+
):
|
|
194
|
+
nve = line_set.data.n_vertex_per_element
|
|
195
|
+
vertices = line_set.data.vertex
|
|
196
|
+
cells = np.c_[np.full(line_set.data.n_elements, nve),
|
|
197
|
+
line_set.data.cells]
|
|
198
|
+
if spline is False:
|
|
199
|
+
mesh = pv.PolyData()
|
|
200
|
+
mesh.points = vertices
|
|
201
|
+
mesh.lines = cells
|
|
202
|
+
else:
|
|
203
|
+
raise NotImplementedError
|
|
204
|
+
|
|
205
|
+
match scalar_type:
|
|
206
|
+
case PyvistaScalarType.POINT:
|
|
207
|
+
mesh.point_data.update(line_set.data.points_attributes_to_dict)
|
|
208
|
+
if active_scalar is not None:
|
|
209
|
+
mesh.set_active_scalars(active_scalar, preference='point')
|
|
210
|
+
case PyvistaScalarType.CELL:
|
|
211
|
+
mesh.cell_data.update(line_set.data.attributes_to_dict)
|
|
212
|
+
if active_scalar is not None:
|
|
213
|
+
mesh.set_active_scalars(active_scalar, preference='cell')
|
|
214
|
+
if as_tube is True:
|
|
215
|
+
return mesh.tube(radius=radius)
|
|
216
|
+
else:
|
|
217
|
+
return mesh
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def to_pyvista_tetra(tetra_mesh: TetraMesh):
|
|
221
|
+
"""Create pyvista.UnstructuredGrid"""
|
|
222
|
+
vertices = tetra_mesh.data.vertex
|
|
223
|
+
tets = tetra_mesh.data.cells
|
|
224
|
+
cells = np.c_[np.full(len(tets), 4), tets]
|
|
225
|
+
import vtk
|
|
226
|
+
ctypes = np.array([vtk.VTK_TETRA, ], np.int32)
|
|
227
|
+
mesh = pv.UnstructuredGrid(cells, ctypes, vertices)
|
|
228
|
+
mesh.cell_data.update(tetra_mesh.data.attributes_to_dict)
|
|
229
|
+
return mesh
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def to_pyvista_grid(
|
|
233
|
+
structured_grid: StructuredGrid,
|
|
234
|
+
data_set_name: str = None,
|
|
235
|
+
attribute_slice: dict = None,
|
|
236
|
+
data_order: str = 'F'
|
|
237
|
+
) -> "pyvista.StructuredGrid":
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
structured_grid:
|
|
242
|
+
data_set_name:
|
|
243
|
+
attribute_slice: dictionary to select which 3D array will be displayed as color
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
|
|
247
|
+
"""
|
|
248
|
+
if attribute_slice is None:
|
|
249
|
+
attribute_slice = dict()
|
|
250
|
+
|
|
251
|
+
if data_set_name is None:
|
|
252
|
+
data_set_name = structured_grid.ds.active_data_array_name
|
|
253
|
+
|
|
254
|
+
cart_dims = structured_grid.cartesian_dimensions
|
|
255
|
+
data_dims = structured_grid.ds.data[data_set_name].sel(**attribute_slice).ndim
|
|
256
|
+
if cart_dims < data_dims:
|
|
257
|
+
raise AttributeError('Data dimension and cartesian dimensions must match.'
|
|
258
|
+
'Possibly there are not valid dimension name in the'
|
|
259
|
+
'xarray.DataArray. These are X Y Z x y z')
|
|
260
|
+
|
|
261
|
+
if data_dims == 2:
|
|
262
|
+
meshgrid = structured_grid.meshgrid_2d(data_set_name)
|
|
263
|
+
elif data_dims == 3:
|
|
264
|
+
meshgrid = structured_grid.meshgrid_3d
|
|
265
|
+
else:
|
|
266
|
+
raise AttributeError('The DataArray does not have valid dimensionality. '
|
|
267
|
+
'Possibly there are not valid dimension name in the'
|
|
268
|
+
'xarray.DataArray. These are X Y Z x y z')
|
|
269
|
+
|
|
270
|
+
pv = optional_requirements.require_pyvista()
|
|
271
|
+
mesh = pv.StructuredGrid(*meshgrid)
|
|
272
|
+
update_grid_attribute(mesh, structured_grid, data_order,
|
|
273
|
+
attribute_slice, data_set_name)
|
|
274
|
+
|
|
275
|
+
return mesh
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def update_grid_attribute(
|
|
279
|
+
mesh: 'pv.StructuredGrid',
|
|
280
|
+
structured_grid: StructuredGrid,
|
|
281
|
+
data_order='F',
|
|
282
|
+
attribute_slice=None,
|
|
283
|
+
data_set_name=None
|
|
284
|
+
):
|
|
285
|
+
if attribute_slice is None:
|
|
286
|
+
attribute_slice = dict()
|
|
287
|
+
|
|
288
|
+
if data_set_name is None:
|
|
289
|
+
data_set_name = structured_grid.ds.active_data_array_name
|
|
290
|
+
import xarray as xr
|
|
291
|
+
dataset: xr.DataArray = structured_grid.ds.data[data_set_name]
|
|
292
|
+
|
|
293
|
+
attributeData = {data_set_name: dataset.sel(**attribute_slice).values.ravel(data_order)}
|
|
294
|
+
mesh.point_data.update(attributeData)
|
|
295
|
+
|
|
296
|
+
return mesh
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _n_cartesian_coord(attribute, structured_grid):
|
|
300
|
+
coord_names = np.array(['X', 'Y', 'Z', 'x', 'y', 'z'])
|
|
301
|
+
ndim = np.isin(coord_names, structured_grid.ds.data[attribute].dims).sum()
|
|
302
|
+
return ndim
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _generate_colors_from_colormap(num_colors, cmap_name='viridis'):
|
|
306
|
+
"""
|
|
307
|
+
Generate a sequence of colors from a given Matplotlib colormap.
|
|
308
|
+
|
|
309
|
+
Parameters:
|
|
310
|
+
num_colors (int): Number of colors to generate.
|
|
311
|
+
cmap_name (str): Name of the Matplotlib colormap to use.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
list of tuple: List of RGB color tuples.
|
|
315
|
+
"""
|
|
316
|
+
import matplotlib.pyplot as plt
|
|
317
|
+
colormap = plt.cm.get_cmap(cmap_name)
|
|
318
|
+
colors = colormap(np.linspace(0, 1, num_colors))
|
|
319
|
+
# Convert from RGBA to RGB and scale to 0-255
|
|
320
|
+
return [(int(r * 255), int(g * 255), int(b * 255)) for r, g, b, _ in colors]
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def base_structs_to_binary_file(path, base_struct, order='F'):
|
|
5
|
-
try:
|
|
6
|
-
bytearray_le, header = base_struct.default_data_array_to_binary_legacy(order=order)
|
|
7
|
-
except AttributeError:
|
|
8
|
-
bytearray_le, header = base_struct.to_binary_legacy(order=order)
|
|
9
|
-
with open(path+'.json', 'w') as outfile:
|
|
10
|
-
json.dump(header, outfile)
|
|
11
|
-
|
|
12
|
-
new_file = open(path+".le", "wb")
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def base_structs_to_binary_file(path, base_struct, order='F'):
|
|
5
|
+
try:
|
|
6
|
+
bytearray_le, header = base_struct.default_data_array_to_binary_legacy(order=order)
|
|
7
|
+
except AttributeError:
|
|
8
|
+
bytearray_le, header = base_struct.to_binary_legacy(order=order)
|
|
9
|
+
with open(path+'.json', 'w') as outfile:
|
|
10
|
+
json.dump(header, outfile)
|
|
11
|
+
|
|
12
|
+
new_file = open(path+".le", "wb")
|
|
13
13
|
new_file.write(bytearray_le)
|