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,319 +1,338 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Union, Dict, Mapping, Hashable, Any, Literal
|
|
3
|
-
|
|
4
|
-
import numpy as np
|
|
5
|
-
import pandas as pd
|
|
6
|
-
import xarray as xr
|
|
7
|
-
|
|
8
|
-
from subsurface.core.structs.base_structures._unstructured_data_constructor import vertex_and_cells_arrays_to_data_array, raw_attributes_to_dict_data_arrays
|
|
9
|
-
from subsurface.core.structs.base_structures.base_structures_enum import SpecialCellCase
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclass(frozen=False)
|
|
13
|
-
class UnstructuredData:
|
|
14
|
-
data: xr.Dataset
|
|
15
|
-
cells_attr_name: str = "cell_attrs"
|
|
16
|
-
vertex_attr_name: str = "vertex_attrs"
|
|
17
|
-
|
|
18
|
-
"""Primary structure definition for unstructured data
|
|
19
|
-
|
|
20
|
-
Attributes:
|
|
21
|
-
data (`xarray.Dataset`): Data structure where we store
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
|
|
25
|
-
ds (xarray.Dataset): Directly a dataset with the expected structured. This
|
|
26
|
-
arg is specially thought for loading data from disk
|
|
27
|
-
|
|
28
|
-
Notes:
|
|
29
|
-
Depending on the shape of `edge` the following unstructured elements can
|
|
30
|
-
be created:
|
|
31
|
-
|
|
32
|
-
- cells NDArray[(Any, 0), IntX] or NDArray[(Any, 1), IntX] -> *Point cloud*.
|
|
33
|
-
E.g. Outcrop scan with lidar
|
|
34
|
-
- cells NDArray[(Any, 2), IntX] -> *Lines*. E.g. Borehole
|
|
35
|
-
- cells NDArray[(Any, 3), IntX] -> *Mesh*. E.g surface-DEM Topography
|
|
36
|
-
- cells NDArray[(Any, 4), IntX]
|
|
37
|
-
- -> *tetrahedron*
|
|
38
|
-
- -> *quadrilateral (or tetragon)* UNSUPPORTED?
|
|
39
|
-
- cells NDArray[(Any, 8), IntX] -> *Hexahedron: Unstructured grid/Prisms*
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
def __post_init__(self):
|
|
43
|
-
self._validate()
|
|
44
|
-
|
|
45
|
-
def __repr__(self):
|
|
46
|
-
return self.data.__repr__()
|
|
47
|
-
|
|
48
|
-
@classmethod
|
|
49
|
-
def from_array(
|
|
50
|
-
cls,
|
|
51
|
-
vertex: np.ndarray,
|
|
52
|
-
cells: Union[np.ndarray, Literal["lines", "points"], SpecialCellCase],
|
|
53
|
-
*,
|
|
54
|
-
cells_attr: Union[None, pd.DataFrame, Dict[str, xr.DataArray]] = None,
|
|
55
|
-
vertex_attr: Union[None, pd.DataFrame, Dict[str, xr.DataArray]] = None,
|
|
56
|
-
coords: Mapping[Hashable, Any] = None,
|
|
57
|
-
xarray_attributes: Mapping[Hashable, Any] = None,
|
|
58
|
-
default_cells_attr_name: str = "cell_attrs",
|
|
59
|
-
default_points_attr_name: str = "vertex_attrs",
|
|
60
|
-
attributes: Union[None, pd.DataFrame, Dict[str, xr.DataArray]] = None # TODO Obsolete
|
|
61
|
-
):
|
|
62
|
-
""" Constructor of UnstructuredData from arrays or pandas DataFrames.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
vertex (np.ndarray): NDArray[(Any, 3), FloatX]: XYZ point data
|
|
66
|
-
cells (Union[np.ndarray, Literal["lines", "points"]]): NDArray[(Any, ...), IntX]:
|
|
67
|
-
Combination of vertex that create different geometric elements. If
|
|
68
|
-
str use default values for either points or lines
|
|
69
|
-
cells_attr (Union[None, pd.DataFrame, Dict[str, xr.DataArray]]: Number associated to an element
|
|
70
|
-
vertex_attr (Union[None, pd.DataFrame, Dict[str, xr.DataArray]]: Number
|
|
71
|
-
associated to points
|
|
72
|
-
coords:
|
|
73
|
-
xarray_attributes:
|
|
74
|
-
attributes:
|
|
75
|
-
default_cells_attr_name:
|
|
76
|
-
default_points_attr_name:
|
|
77
|
-
|
|
78
|
-
Returns:
|
|
79
|
-
|
|
80
|
-
"""
|
|
81
|
-
if attributes is not None:
|
|
82
|
-
cells_attr = attributes
|
|
83
|
-
|
|
84
|
-
cells_data_array, n_cells, n_vertex, vertex_data_array = vertex_and_cells_arrays_to_data_array(
|
|
85
|
-
cells=cells,
|
|
86
|
-
vertex=vertex
|
|
87
|
-
)
|
|
88
|
-
points_attributes_xarray_dict: dict[str, xr.DataArray] = raw_attributes_to_dict_data_arrays(
|
|
89
|
-
default_attributes_name=default_points_attr_name,
|
|
90
|
-
n_items=n_vertex,
|
|
91
|
-
dims=["points", "vertex_attr"],
|
|
92
|
-
raw_attributes=vertex_attr
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
cells_attributes_xarray_dict: dict[str, xr.DataArray] = raw_attributes_to_dict_data_arrays(
|
|
96
|
-
default_attributes_name=default_cells_attr_name,
|
|
97
|
-
n_items=n_cells,
|
|
98
|
-
dims=["cell", "cell_attr"],
|
|
99
|
-
raw_attributes=cells_attr
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
xarray_dict = {
|
|
103
|
-
"vertex": vertex_data_array,
|
|
104
|
-
"cells" : cells_data_array,
|
|
105
|
-
**cells_attributes_xarray_dict,
|
|
106
|
-
**points_attributes_xarray_dict
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return cls.from_data_arrays_dict(
|
|
110
|
-
xarray_dict=xarray_dict,
|
|
111
|
-
coords=coords,
|
|
112
|
-
xarray_attributes=xarray_attributes,
|
|
113
|
-
default_cells_attributes_name=default_cells_attr_name,
|
|
114
|
-
default_points_attributes_name=default_points_attr_name
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
@classmethod
|
|
118
|
-
def from_data_arrays_dict(
|
|
119
|
-
cls,
|
|
120
|
-
xarray_dict: Dict[str, xr.DataArray],
|
|
121
|
-
coords: Mapping[Hashable, Any] = None,
|
|
122
|
-
xarray_attributes: Mapping[Hashable, Any] = None,
|
|
123
|
-
default_cells_attributes_name="cell_attrs",
|
|
124
|
-
default_points_attributes_name="vertex_attrs"
|
|
125
|
-
):
|
|
126
|
-
# TODO: xr.Dataset seems to have been changed with 2022.06. needs to be adapted for indexing
|
|
127
|
-
ds = xr.Dataset(xarray_dict, coords=coords, attrs=xarray_attributes)
|
|
128
|
-
|
|
129
|
-
# Try to unstack pandas dataframe if exist
|
|
130
|
-
# TODO: This is an issue in wells. If it is only there maybe we should move it there
|
|
131
|
-
try:
|
|
132
|
-
ds = ds.reset_index('cell')
|
|
133
|
-
except (KeyError, ValueError) as e:
|
|
134
|
-
print(f"{e} xarray dataset must include 'cell' key (KeyError) or xarray 'cell' has no index (ValueError).")
|
|
135
|
-
|
|
136
|
-
# Check that the Dataset data_vars names matches, "vertex", "cells", default_cells_attributes_name and default_points_attributes_name
|
|
137
|
-
# and raise an error pointing out which one is missing
|
|
138
|
-
_vars = [var in ds.data_vars for var in ["vertex", "cells", default_cells_attributes_name, default_points_attributes_name]]
|
|
139
|
-
if not all(_vars):
|
|
140
|
-
missing = ["vertex", "cells", default_cells_attributes_name, default_points_attributes_name]
|
|
141
|
-
raise KeyError(f"DataArray must include the following keys: {missing}. For attributes you can"
|
|
142
|
-
f" change the default names with default_cells_attributes_name and default_points_attributes_name.")
|
|
143
|
-
|
|
144
|
-
return cls(ds, default_cells_attributes_name, default_points_attributes_name)
|
|
145
|
-
|
|
146
|
-
@classmethod
|
|
147
|
-
def from_binary_le(cls, path: str):
|
|
148
|
-
from ._liquid_earth_mesh import LiquidEarthMesh
|
|
149
|
-
with open(path, 'rb') as f:
|
|
150
|
-
bytes_data = f.read()
|
|
151
|
-
mesh = LiquidEarthMesh.from_binary(bytes_data)
|
|
152
|
-
unstruct = cls.from_array(
|
|
153
|
-
vertex=mesh.vertex,
|
|
154
|
-
cells=mesh.cells,
|
|
155
|
-
cells_attr=mesh.attributes,
|
|
156
|
-
vertex_attr=mesh.points_attributes,
|
|
157
|
-
xarray_attributes=None
|
|
158
|
-
)
|
|
159
|
-
return unstruct
|
|
160
|
-
|
|
161
|
-
@classmethod
|
|
162
|
-
def from_binary_le_legacy(cls, path_to_binary: str, path_to_json: str):
|
|
163
|
-
import json
|
|
164
|
-
from ._liquid_earth_mesh import LiquidEarthMesh
|
|
165
|
-
|
|
166
|
-
with open(path_to_binary, 'rb') as f:
|
|
167
|
-
body_ = f.read()
|
|
168
|
-
with open(path_to_json, 'r') as f:
|
|
169
|
-
header_ = json.load(f)
|
|
170
|
-
|
|
171
|
-
header_json = json.dumps(header_)
|
|
172
|
-
header_json_bytes = header_json.encode('utf-8')
|
|
173
|
-
header_json_length = len(header_json_bytes)
|
|
174
|
-
header_json_length_bytes = header_json_length.to_bytes(4, byteorder='little')
|
|
175
|
-
file = header_json_length_bytes + header_json_bytes + body_
|
|
176
|
-
mesh = LiquidEarthMesh.from_binary(file)
|
|
177
|
-
unstruct = cls.from_array(
|
|
178
|
-
vertex=mesh.vertex,
|
|
179
|
-
cells=mesh.cells,
|
|
180
|
-
cells_attr=mesh.attributes,
|
|
181
|
-
vertex_attr=mesh.points_attributes,
|
|
182
|
-
xarray_attributes=None
|
|
183
|
-
)
|
|
184
|
-
return unstruct
|
|
185
|
-
|
|
186
|
-
@property
|
|
187
|
-
def vertex(self) -> np.ndarray:
|
|
188
|
-
return self.data['vertex'].values
|
|
189
|
-
|
|
190
|
-
@property
|
|
191
|
-
def cells(self):
|
|
192
|
-
return self.data['cells'].values
|
|
193
|
-
|
|
194
|
-
@property
|
|
195
|
-
def attributes(self) -> pd.DataFrame:
|
|
196
|
-
xarray = self.data[self.cells_attr_name]
|
|
197
|
-
return xarray.to_dataframe()[self.cells_attr_name].unstack(level=1)
|
|
198
|
-
|
|
199
|
-
@attributes.setter
|
|
200
|
-
def attributes(self, dataframe):
|
|
201
|
-
self.data[self.cells_attr_name] = xr.DataArray(dataframe, dims=['element', 'cell_attr'])
|
|
202
|
-
|
|
203
|
-
@property
|
|
204
|
-
def cell_attributes(self):
|
|
205
|
-
return self.attributes
|
|
206
|
-
|
|
207
|
-
@cell_attributes.setter
|
|
208
|
-
def cell_attributes(self, dataframe):
|
|
209
|
-
self.attributes = dataframe
|
|
210
|
-
|
|
211
|
-
@property
|
|
212
|
-
def points_attributes(self) -> pd.DataFrame:
|
|
213
|
-
data_array: xr.DataArray = self.data[self.vertex_attr_name]
|
|
214
|
-
dataframe: pd.DataFrame = data_array.to_dataframe()
|
|
215
|
-
not_suer = dataframe[self.vertex_attr_name]
|
|
216
|
-
unstack = not_suer.unstack(level=1)
|
|
217
|
-
return unstack
|
|
218
|
-
|
|
219
|
-
@points_attributes.setter
|
|
220
|
-
def points_attributes(self, dataframe: pd.DataFrame):
|
|
221
|
-
vertex_attr: xr.DataArray = self.data[self.vertex_attr_name]
|
|
222
|
-
vertex_attr.values = dataframe.values
|
|
223
|
-
|
|
224
|
-
@property
|
|
225
|
-
def n_elements(self):
|
|
226
|
-
return self.cells.shape[0]
|
|
227
|
-
|
|
228
|
-
@property
|
|
229
|
-
def n_vertex_per_element(self):
|
|
230
|
-
return self.cells.shape[1]
|
|
231
|
-
|
|
232
|
-
@property
|
|
233
|
-
def n_points(self):
|
|
234
|
-
return self.vertex.shape[0]
|
|
235
|
-
|
|
236
|
-
@property
|
|
237
|
-
def attributes_to_dict(
|
|
238
|
-
self,
|
|
239
|
-
orient: Literal["dict", "list", "series", "split", "tight", "index"] = "list"
|
|
240
|
-
):
|
|
241
|
-
return self.attributes.to_dict(orient)
|
|
242
|
-
|
|
243
|
-
@property
|
|
244
|
-
def points_attributes_to_dict(
|
|
245
|
-
self,
|
|
246
|
-
orient: Literal["dict", "list", "series", "split", "tight", "index"] = "list"
|
|
247
|
-
):
|
|
248
|
-
return self.points_attributes.to_dict(orient)
|
|
249
|
-
|
|
250
|
-
@property
|
|
251
|
-
def extent(self):
|
|
252
|
-
max = self.vertex.max(axis=0)
|
|
253
|
-
min = self.vertex.min(axis=0)
|
|
254
|
-
extent = np.stack((min, max), axis=1).ravel()
|
|
255
|
-
return extent
|
|
256
|
-
|
|
257
|
-
def to_xarray(self):
|
|
258
|
-
a = xr.DataArray(self.vertex, dims=['points', 'XYZ'])
|
|
259
|
-
b = xr.DataArray(self.cells, dims=['cells', 'node'])
|
|
260
|
-
e = xr.DataArray(self.attributes, dims=['element', 'cell_attr'])
|
|
261
|
-
c = xr.Dataset({'v': a, 'e': b, 'a': e})
|
|
262
|
-
return c
|
|
263
|
-
|
|
264
|
-
def to_binary_legacy(self, order='F'):
|
|
265
|
-
bytearray_le = self._to_bytearray(order)
|
|
266
|
-
header = self._set_binary_header()
|
|
267
|
-
return bytearray_le, header
|
|
268
|
-
|
|
269
|
-
def to_binary(self, order='F') -> bytes:
|
|
270
|
-
body_ = self._to_bytearray(order)
|
|
271
|
-
header_ = self._set_binary_header()
|
|
272
|
-
import json
|
|
273
|
-
header_json = json.dumps(header_)
|
|
274
|
-
header_json_bytes = header_json.encode('utf-8')
|
|
275
|
-
header_json_length = len(header_json_bytes)
|
|
276
|
-
header_json_length_bytes = header_json_length.to_bytes(4, byteorder='little')
|
|
277
|
-
file = header_json_length_bytes + header_json_bytes + body_
|
|
278
|
-
return file
|
|
279
|
-
|
|
280
|
-
def _set_binary_header(self):
|
|
281
|
-
header = {
|
|
282
|
-
"vertex_shape" : self.vertex.shape,
|
|
283
|
-
"cell_shape" : self.cells.shape,
|
|
284
|
-
"cell_attr_shape" : self.attributes.shape,
|
|
285
|
-
"vertex_attr_shape": self.points_attributes.shape,
|
|
286
|
-
"cell_attr_names" : self.attributes.columns.to_list(),
|
|
287
|
-
"cell_attr_types" : self.attributes.dtypes.astype(str).to_list(),
|
|
288
|
-
"vertex_attr_names": self.points_attributes.columns.to_list(),
|
|
289
|
-
"vertex_attr_types": self.attributes.dtypes.astype(str).to_list(),
|
|
290
|
-
"xarray_attrs" : self.data.attrs
|
|
291
|
-
}
|
|
292
|
-
return header
|
|
293
|
-
|
|
294
|
-
def _to_bytearray(self, order):
|
|
295
|
-
vertex = self.vertex.astype('float32').tobytes(order)
|
|
296
|
-
cells = self.cells.astype('int32').tobytes(order)
|
|
297
|
-
cell_attribute = self.attributes.values.astype('float32').tobytes(order)
|
|
298
|
-
vertex_attribute = self.points_attributes.values.astype('float32').tobytes(order)
|
|
299
|
-
bytearray_le = vertex + cells + cell_attribute + vertex_attribute
|
|
300
|
-
return bytearray_le
|
|
301
|
-
|
|
302
|
-
def
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Union, Dict, Mapping, Hashable, Any, Literal
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import xarray as xr
|
|
7
|
+
|
|
8
|
+
from subsurface.core.structs.base_structures._unstructured_data_constructor import vertex_and_cells_arrays_to_data_array, raw_attributes_to_dict_data_arrays
|
|
9
|
+
from subsurface.core.structs.base_structures.base_structures_enum import SpecialCellCase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=False)
|
|
13
|
+
class UnstructuredData:
|
|
14
|
+
data: xr.Dataset
|
|
15
|
+
cells_attr_name: str = "cell_attrs"
|
|
16
|
+
vertex_attr_name: str = "vertex_attrs"
|
|
17
|
+
|
|
18
|
+
"""Primary structure definition for unstructured data
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
data (`xarray.Dataset`): Data structure where we store
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
|
|
25
|
+
ds (xarray.Dataset): Directly a dataset with the expected structured. This
|
|
26
|
+
arg is specially thought for loading data from disk
|
|
27
|
+
|
|
28
|
+
Notes:
|
|
29
|
+
Depending on the shape of `edge` the following unstructured elements can
|
|
30
|
+
be created:
|
|
31
|
+
|
|
32
|
+
- cells NDArray[(Any, 0), IntX] or NDArray[(Any, 1), IntX] -> *Point cloud*.
|
|
33
|
+
E.g. Outcrop scan with lidar
|
|
34
|
+
- cells NDArray[(Any, 2), IntX] -> *Lines*. E.g. Borehole
|
|
35
|
+
- cells NDArray[(Any, 3), IntX] -> *Mesh*. E.g surface-DEM Topography
|
|
36
|
+
- cells NDArray[(Any, 4), IntX]
|
|
37
|
+
- -> *tetrahedron*
|
|
38
|
+
- -> *quadrilateral (or tetragon)* UNSUPPORTED?
|
|
39
|
+
- cells NDArray[(Any, 8), IntX] -> *Hexahedron: Unstructured grid/Prisms*
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __post_init__(self):
|
|
43
|
+
self._validate()
|
|
44
|
+
|
|
45
|
+
def __repr__(self):
|
|
46
|
+
return self.data.__repr__()
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_array(
|
|
50
|
+
cls,
|
|
51
|
+
vertex: np.ndarray,
|
|
52
|
+
cells: Union[np.ndarray, Literal["lines", "points"], SpecialCellCase],
|
|
53
|
+
*,
|
|
54
|
+
cells_attr: Union[None, pd.DataFrame, Dict[str, xr.DataArray]] = None,
|
|
55
|
+
vertex_attr: Union[None, pd.DataFrame, Dict[str, xr.DataArray]] = None,
|
|
56
|
+
coords: Mapping[Hashable, Any] = None,
|
|
57
|
+
xarray_attributes: Mapping[Hashable, Any] = None,
|
|
58
|
+
default_cells_attr_name: str = "cell_attrs",
|
|
59
|
+
default_points_attr_name: str = "vertex_attrs",
|
|
60
|
+
attributes: Union[None, pd.DataFrame, Dict[str, xr.DataArray]] = None # TODO Obsolete
|
|
61
|
+
):
|
|
62
|
+
""" Constructor of UnstructuredData from arrays or pandas DataFrames.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
vertex (np.ndarray): NDArray[(Any, 3), FloatX]: XYZ point data
|
|
66
|
+
cells (Union[np.ndarray, Literal["lines", "points"]]): NDArray[(Any, ...), IntX]:
|
|
67
|
+
Combination of vertex that create different geometric elements. If
|
|
68
|
+
str use default values for either points or lines
|
|
69
|
+
cells_attr (Union[None, pd.DataFrame, Dict[str, xr.DataArray]]: Number associated to an element
|
|
70
|
+
vertex_attr (Union[None, pd.DataFrame, Dict[str, xr.DataArray]]: Number
|
|
71
|
+
associated to points
|
|
72
|
+
coords:
|
|
73
|
+
xarray_attributes:
|
|
74
|
+
attributes:
|
|
75
|
+
default_cells_attr_name:
|
|
76
|
+
default_points_attr_name:
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
if attributes is not None:
|
|
82
|
+
cells_attr = attributes
|
|
83
|
+
|
|
84
|
+
cells_data_array, n_cells, n_vertex, vertex_data_array = vertex_and_cells_arrays_to_data_array(
|
|
85
|
+
cells=cells,
|
|
86
|
+
vertex=vertex
|
|
87
|
+
)
|
|
88
|
+
points_attributes_xarray_dict: dict[str, xr.DataArray] = raw_attributes_to_dict_data_arrays(
|
|
89
|
+
default_attributes_name=default_points_attr_name,
|
|
90
|
+
n_items=n_vertex,
|
|
91
|
+
dims=["points", "vertex_attr"],
|
|
92
|
+
raw_attributes=vertex_attr
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
cells_attributes_xarray_dict: dict[str, xr.DataArray] = raw_attributes_to_dict_data_arrays(
|
|
96
|
+
default_attributes_name=default_cells_attr_name,
|
|
97
|
+
n_items=n_cells,
|
|
98
|
+
dims=["cell", "cell_attr"],
|
|
99
|
+
raw_attributes=cells_attr
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
xarray_dict = {
|
|
103
|
+
"vertex": vertex_data_array,
|
|
104
|
+
"cells" : cells_data_array,
|
|
105
|
+
**cells_attributes_xarray_dict,
|
|
106
|
+
**points_attributes_xarray_dict
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return cls.from_data_arrays_dict(
|
|
110
|
+
xarray_dict=xarray_dict,
|
|
111
|
+
coords=coords,
|
|
112
|
+
xarray_attributes=xarray_attributes,
|
|
113
|
+
default_cells_attributes_name=default_cells_attr_name,
|
|
114
|
+
default_points_attributes_name=default_points_attr_name
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def from_data_arrays_dict(
|
|
119
|
+
cls,
|
|
120
|
+
xarray_dict: Dict[str, xr.DataArray],
|
|
121
|
+
coords: Mapping[Hashable, Any] = None,
|
|
122
|
+
xarray_attributes: Mapping[Hashable, Any] = None,
|
|
123
|
+
default_cells_attributes_name="cell_attrs",
|
|
124
|
+
default_points_attributes_name="vertex_attrs"
|
|
125
|
+
):
|
|
126
|
+
# TODO: xr.Dataset seems to have been changed with 2022.06. needs to be adapted for indexing
|
|
127
|
+
ds = xr.Dataset(xarray_dict, coords=coords, attrs=xarray_attributes)
|
|
128
|
+
|
|
129
|
+
# Try to unstack pandas dataframe if exist
|
|
130
|
+
# TODO: This is an issue in wells. If it is only there maybe we should move it there
|
|
131
|
+
try:
|
|
132
|
+
ds = ds.reset_index('cell')
|
|
133
|
+
except (KeyError, ValueError) as e:
|
|
134
|
+
print(f"{e} xarray dataset must include 'cell' key (KeyError) or xarray 'cell' has no index (ValueError).")
|
|
135
|
+
|
|
136
|
+
# Check that the Dataset data_vars names matches, "vertex", "cells", default_cells_attributes_name and default_points_attributes_name
|
|
137
|
+
# and raise an error pointing out which one is missing
|
|
138
|
+
_vars = [var in ds.data_vars for var in ["vertex", "cells", default_cells_attributes_name, default_points_attributes_name]]
|
|
139
|
+
if not all(_vars):
|
|
140
|
+
missing = ["vertex", "cells", default_cells_attributes_name, default_points_attributes_name]
|
|
141
|
+
raise KeyError(f"DataArray must include the following keys: {missing}. For attributes you can"
|
|
142
|
+
f" change the default names with default_cells_attributes_name and default_points_attributes_name.")
|
|
143
|
+
|
|
144
|
+
return cls(ds, default_cells_attributes_name, default_points_attributes_name)
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
def from_binary_le(cls, path: str):
|
|
148
|
+
from ._liquid_earth_mesh import LiquidEarthMesh
|
|
149
|
+
with open(path, 'rb') as f:
|
|
150
|
+
bytes_data = f.read()
|
|
151
|
+
mesh = LiquidEarthMesh.from_binary(bytes_data)
|
|
152
|
+
unstruct = cls.from_array(
|
|
153
|
+
vertex=mesh.vertex,
|
|
154
|
+
cells=mesh.cells,
|
|
155
|
+
cells_attr=mesh.attributes,
|
|
156
|
+
vertex_attr=mesh.points_attributes,
|
|
157
|
+
xarray_attributes=None
|
|
158
|
+
)
|
|
159
|
+
return unstruct
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def from_binary_le_legacy(cls, path_to_binary: str, path_to_json: str):
|
|
163
|
+
import json
|
|
164
|
+
from ._liquid_earth_mesh import LiquidEarthMesh
|
|
165
|
+
|
|
166
|
+
with open(path_to_binary, 'rb') as f:
|
|
167
|
+
body_ = f.read()
|
|
168
|
+
with open(path_to_json, 'r') as f:
|
|
169
|
+
header_ = json.load(f)
|
|
170
|
+
|
|
171
|
+
header_json = json.dumps(header_)
|
|
172
|
+
header_json_bytes = header_json.encode('utf-8')
|
|
173
|
+
header_json_length = len(header_json_bytes)
|
|
174
|
+
header_json_length_bytes = header_json_length.to_bytes(4, byteorder='little')
|
|
175
|
+
file = header_json_length_bytes + header_json_bytes + body_
|
|
176
|
+
mesh = LiquidEarthMesh.from_binary(file)
|
|
177
|
+
unstruct = cls.from_array(
|
|
178
|
+
vertex=mesh.vertex,
|
|
179
|
+
cells=mesh.cells,
|
|
180
|
+
cells_attr=mesh.attributes,
|
|
181
|
+
vertex_attr=mesh.points_attributes,
|
|
182
|
+
xarray_attributes=None
|
|
183
|
+
)
|
|
184
|
+
return unstruct
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def vertex(self) -> np.ndarray:
|
|
188
|
+
return self.data['vertex'].values
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def cells(self):
|
|
192
|
+
return self.data['cells'].values
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def attributes(self) -> pd.DataFrame:
|
|
196
|
+
xarray = self.data[self.cells_attr_name]
|
|
197
|
+
return xarray.to_dataframe()[self.cells_attr_name].unstack(level=1)
|
|
198
|
+
|
|
199
|
+
@attributes.setter
|
|
200
|
+
def attributes(self, dataframe):
|
|
201
|
+
self.data[self.cells_attr_name] = xr.DataArray(dataframe, dims=['element', 'cell_attr'])
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def cell_attributes(self):
|
|
205
|
+
return self.attributes
|
|
206
|
+
|
|
207
|
+
@cell_attributes.setter
|
|
208
|
+
def cell_attributes(self, dataframe):
|
|
209
|
+
self.attributes = dataframe
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def points_attributes(self) -> pd.DataFrame:
|
|
213
|
+
data_array: xr.DataArray = self.data[self.vertex_attr_name]
|
|
214
|
+
dataframe: pd.DataFrame = data_array.to_dataframe()
|
|
215
|
+
not_suer = dataframe[self.vertex_attr_name]
|
|
216
|
+
unstack = not_suer.unstack(level=1)
|
|
217
|
+
return unstack
|
|
218
|
+
|
|
219
|
+
@points_attributes.setter
|
|
220
|
+
def points_attributes(self, dataframe: pd.DataFrame):
|
|
221
|
+
vertex_attr: xr.DataArray = self.data[self.vertex_attr_name]
|
|
222
|
+
vertex_attr.values = dataframe.values
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def n_elements(self):
|
|
226
|
+
return self.cells.shape[0]
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def n_vertex_per_element(self):
|
|
230
|
+
return self.cells.shape[1]
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def n_points(self):
|
|
234
|
+
return self.vertex.shape[0]
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def attributes_to_dict(
|
|
238
|
+
self,
|
|
239
|
+
orient: Literal["dict", "list", "series", "split", "tight", "index"] = "list"
|
|
240
|
+
):
|
|
241
|
+
return self.attributes.to_dict(orient)
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def points_attributes_to_dict(
|
|
245
|
+
self,
|
|
246
|
+
orient: Literal["dict", "list", "series", "split", "tight", "index"] = "list"
|
|
247
|
+
):
|
|
248
|
+
return self.points_attributes.to_dict(orient)
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def extent(self):
|
|
252
|
+
max = self.vertex.max(axis=0)
|
|
253
|
+
min = self.vertex.min(axis=0)
|
|
254
|
+
extent = np.stack((min, max), axis=1).ravel()
|
|
255
|
+
return extent
|
|
256
|
+
|
|
257
|
+
def to_xarray(self):
|
|
258
|
+
a = xr.DataArray(self.vertex, dims=['points', 'XYZ'])
|
|
259
|
+
b = xr.DataArray(self.cells, dims=['cells', 'node'])
|
|
260
|
+
e = xr.DataArray(self.attributes, dims=['element', 'cell_attr'])
|
|
261
|
+
c = xr.Dataset({'v': a, 'e': b, 'a': e})
|
|
262
|
+
return c
|
|
263
|
+
|
|
264
|
+
def to_binary_legacy(self, order='F'):
|
|
265
|
+
bytearray_le = self._to_bytearray(order)
|
|
266
|
+
header = self._set_binary_header()
|
|
267
|
+
return bytearray_le, header
|
|
268
|
+
|
|
269
|
+
def to_binary(self, order='F') -> bytes:
|
|
270
|
+
body_ = self._to_bytearray(order)
|
|
271
|
+
header_ = self._set_binary_header()
|
|
272
|
+
import json
|
|
273
|
+
header_json = json.dumps(header_)
|
|
274
|
+
header_json_bytes = header_json.encode('utf-8')
|
|
275
|
+
header_json_length = len(header_json_bytes)
|
|
276
|
+
header_json_length_bytes = header_json_length.to_bytes(4, byteorder='little')
|
|
277
|
+
file = header_json_length_bytes + header_json_bytes + body_
|
|
278
|
+
return file
|
|
279
|
+
|
|
280
|
+
def _set_binary_header(self):
|
|
281
|
+
header = {
|
|
282
|
+
"vertex_shape" : self.vertex.shape,
|
|
283
|
+
"cell_shape" : self.cells.shape,
|
|
284
|
+
"cell_attr_shape" : self.attributes.shape,
|
|
285
|
+
"vertex_attr_shape": self.points_attributes.shape,
|
|
286
|
+
"cell_attr_names" : self.attributes.columns.to_list(),
|
|
287
|
+
"cell_attr_types" : self.attributes.dtypes.astype(str).to_list(),
|
|
288
|
+
"vertex_attr_names": self.points_attributes.columns.to_list(),
|
|
289
|
+
"vertex_attr_types": self.attributes.dtypes.astype(str).to_list(),
|
|
290
|
+
"xarray_attrs" : self.data.attrs
|
|
291
|
+
}
|
|
292
|
+
return header
|
|
293
|
+
|
|
294
|
+
def _to_bytearray(self, order):
|
|
295
|
+
vertex = self.vertex.astype('float32').tobytes(order)
|
|
296
|
+
cells = self.cells.astype('int32').tobytes(order)
|
|
297
|
+
cell_attribute = self.attributes.values.astype('float32').tobytes(order)
|
|
298
|
+
vertex_attribute = self.points_attributes.values.astype('float32').tobytes(order)
|
|
299
|
+
bytearray_le = vertex + cells + cell_attribute + vertex_attribute
|
|
300
|
+
return bytearray_le
|
|
301
|
+
|
|
302
|
+
def _to_bytearray(self, order):
|
|
303
|
+
from subsurface.core.structs.base_structures._aux import safe_convert_to_float32
|
|
304
|
+
vertex = self.vertex.astype('float32').tobytes(order)
|
|
305
|
+
cells = self.cells.astype('int32').tobytes(order)
|
|
306
|
+
|
|
307
|
+
# Only include numeric columns
|
|
308
|
+
cell_attribute = safe_convert_to_float32(
|
|
309
|
+
self.attributes,
|
|
310
|
+
error_handling='drop'
|
|
311
|
+
).values.tobytes(order)
|
|
312
|
+
|
|
313
|
+
vertex_attribute = safe_convert_to_float32(
|
|
314
|
+
self.points_attributes,
|
|
315
|
+
error_handling='drop'
|
|
316
|
+
).values.tobytes(order)
|
|
317
|
+
|
|
318
|
+
bytearray_le = vertex + cells + cell_attribute + vertex_attribute
|
|
319
|
+
return bytearray_le
|
|
320
|
+
|
|
321
|
+
def _validate(self):
|
|
322
|
+
try:
|
|
323
|
+
_ = self.data[self.cells_attr_name]['cell']
|
|
324
|
+
_ = self.data[self.cells_attr_name]['cell_attr']
|
|
325
|
+
except KeyError:
|
|
326
|
+
raise KeyError('Cell attribute DataArrays must contain dimension cell and cell_attr')
|
|
327
|
+
try:
|
|
328
|
+
_ = self.data[self.vertex_attr_name]['vertex_attr']
|
|
329
|
+
_ = self.data[self.vertex_attr_name]['points']
|
|
330
|
+
except KeyError:
|
|
331
|
+
raise KeyError('Point attribute DataArrays must contain dimensions points and vertex_attr.')
|
|
332
|
+
|
|
333
|
+
# Make sure the number of vertices matches the associated data.
|
|
334
|
+
if self.data['cells']['cell'].size != self.data[self.cells_attr_name]['cell'].size:
|
|
335
|
+
raise AttributeError('Attributes and cells must have the same length.')
|
|
336
|
+
|
|
337
|
+
if self.n_points != self.data[self.vertex_attr_name]['points'].size:
|
|
338
|
+
raise AttributeError('points_attributes and vertex must have the same length.')
|