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,282 +1,282 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from typing import Dict, List, Tuple, Union, Literal
|
|
4
|
-
|
|
5
|
-
import numpy as np
|
|
6
|
-
import xarray as xr
|
|
7
|
-
|
|
8
|
-
from ....optional_requirements import require_pyvista
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class StructuredDataType(enum.Enum):
|
|
12
|
-
REGULAR_AXIS_ALIGNED = 0 #: Regular axis aligned grid. Distance between consecutive points is constant
|
|
13
|
-
REGULAR_AXIS_UNALIGNED = 1 #: Regular axis unaligned grid. Distance between consecutive points is constant
|
|
14
|
-
IRREGULAR_AXIS_ALIGNED = 2 #: Irregular axis aligned grid. Distance between consecutive points is not constant
|
|
15
|
-
IRREGULAR_AXIS_UNALIGNED = 3 #: Irregular axis unaligned grid. Distance between consecutive points is not constant
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@dataclass(frozen=False)
|
|
19
|
-
class StructuredData:
|
|
20
|
-
data: xr.Dataset
|
|
21
|
-
_active_data_array_name: str = "data_array"
|
|
22
|
-
type: StructuredDataType = StructuredDataType.REGULAR_AXIS_ALIGNED
|
|
23
|
-
dtype: Literal["float32", "float64"] = "float32"
|
|
24
|
-
|
|
25
|
-
"""Primary structure definition for structured data
|
|
26
|
-
|
|
27
|
-
Check out other constructors: `StructuredData.from_numpy`,
|
|
28
|
-
`StructuredData.from_data_array` and `StructuredData.from_dict`
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
data (xr.Dataset): object containing
|
|
32
|
-
structured data, i.e. data that can be stored in multidimensional
|
|
33
|
-
numpy array. The preferred type to pass as data is directly a
|
|
34
|
-
xr.Dataset to be sure all the attributes are set and named as the user
|
|
35
|
-
wants.
|
|
36
|
-
data_array_name (str): If data is a numpy array or xarray DataArray, data_name
|
|
37
|
-
provides the name for the xarray data variable
|
|
38
|
-
|
|
39
|
-
Attributes:
|
|
40
|
-
data (xarray.Dataset)
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
@property
|
|
44
|
-
def active_data_array_name(self):
|
|
45
|
-
data_var_list = list(self.data.data_vars.keys())
|
|
46
|
-
if self._active_data_array_name not in data_var_list:
|
|
47
|
-
raise ValueError("data_array_name not found in data_vars: {}".format(data_var_list))
|
|
48
|
-
return self._active_data_array_name
|
|
49
|
-
|
|
50
|
-
@active_data_array_name.setter
|
|
51
|
-
def active_data_array_name(self, data_array_name: str):
|
|
52
|
-
self._active_data_array_name = data_array_name
|
|
53
|
-
|
|
54
|
-
@classmethod
|
|
55
|
-
def from_numpy(cls, array: np.ndarray, coords: dict = None, data_array_name: str = "data_array",
|
|
56
|
-
dim_names: List[str] = None):
|
|
57
|
-
if dim_names is None:
|
|
58
|
-
dim_names = cls._default_dim_names(array.ndim)
|
|
59
|
-
# if they are more than 3 we do not know the dimension name but it should valid:
|
|
60
|
-
|
|
61
|
-
dataset: xr.Dataset = xr.Dataset(
|
|
62
|
-
data_vars=
|
|
63
|
-
{
|
|
64
|
-
data_array_name: (dim_names, array)
|
|
65
|
-
},
|
|
66
|
-
coords=coords
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
return cls(dataset, data_array_name)
|
|
70
|
-
|
|
71
|
-
@classmethod
|
|
72
|
-
def from_data_array(cls, data_array: xr.DataArray, data_array_name: str = "data_array"):
|
|
73
|
-
dataset: xr.Dataset = xr.Dataset(
|
|
74
|
-
data_vars={
|
|
75
|
-
data_array_name: data_array
|
|
76
|
-
},
|
|
77
|
-
coords=data_array.coords
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
return cls(dataset, data_array_name)
|
|
81
|
-
|
|
82
|
-
@classmethod
|
|
83
|
-
def from_dict(cls, data_dict: Dict[str, xr.DataArray], coords: Dict[str, str] = None, data_array_name: str = "data_array"):
|
|
84
|
-
dataset: xr.Dataset = xr.Dataset(data_vars=data_dict, coords=coords)
|
|
85
|
-
return cls(dataset, data_array_name)
|
|
86
|
-
|
|
87
|
-
@classmethod
|
|
88
|
-
def from_pyvista_structured_grid(
|
|
89
|
-
cls,
|
|
90
|
-
grid: Union["pyvista.ExplicitStructuredGrid", "pyvista.StructuredGrid"],
|
|
91
|
-
data_array_name: str = "data_array"
|
|
92
|
-
):
|
|
93
|
-
pyvista = require_pyvista()
|
|
94
|
-
# Extract p
|
|
95
|
-
|
|
96
|
-
# Extract cell data and point data (if any)
|
|
97
|
-
data_vars = {}
|
|
98
|
-
|
|
99
|
-
# TODO: I need to do something with the bounds
|
|
100
|
-
|
|
101
|
-
dimensions = np.array(grid.dimensions) - 1
|
|
102
|
-
default_dim_names = cls._default_dim_names(dimensions.shape[0])
|
|
103
|
-
|
|
104
|
-
bounds: tuple = grid.bounds
|
|
105
|
-
coords = {}
|
|
106
|
-
for i, dim in enumerate(default_dim_names):
|
|
107
|
-
coords[dim] = np.linspace(
|
|
108
|
-
start=bounds[i * 2],
|
|
109
|
-
stop=bounds[i * 2 + 1],
|
|
110
|
-
num=dimensions[i],
|
|
111
|
-
endpoint=False
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
for name in grid.cell_data:
|
|
115
|
-
cell_attr_data: pyvista.pyvista_ndarray = grid[name]
|
|
116
|
-
cell_attr_data_reshaped = cell_attr_data.reshape(dimensions, order='F')
|
|
117
|
-
|
|
118
|
-
data_vars[name] = xr.DataArray(
|
|
119
|
-
data=cell_attr_data_reshaped,
|
|
120
|
-
dims=default_dim_names,
|
|
121
|
-
name=name
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
dataset: xr.Dataset = xr.Dataset(
|
|
125
|
-
data_vars=data_vars,
|
|
126
|
-
coords=coords
|
|
127
|
-
)
|
|
128
|
-
return cls(dataset, data_array_name)
|
|
129
|
-
|
|
130
|
-
@property
|
|
131
|
-
def values(self) -> np.ndarray:
|
|
132
|
-
return self.data[self.active_data_array_name].values
|
|
133
|
-
|
|
134
|
-
_bounds: Tuple[float, float, float, float, float, float] = None
|
|
135
|
-
|
|
136
|
-
@property
|
|
137
|
-
def bounds(self):
|
|
138
|
-
if self._bounds is not None:
|
|
139
|
-
return self._bounds
|
|
140
|
-
|
|
141
|
-
array_: xr.DataArray = self.data[self.active_data_array_name]
|
|
142
|
-
bounds = self._get_bounds_from_coord(array_)
|
|
143
|
-
return bounds
|
|
144
|
-
|
|
145
|
-
@bounds.setter
|
|
146
|
-
def bounds(self, bounds: Tuple[float, float, float, float, float, float]):
|
|
147
|
-
"""
|
|
148
|
-
Set the bounds of the structured data. This is useful for defining the
|
|
149
|
-
spatial extent of the data in a structured grid.
|
|
150
|
-
|
|
151
|
-
Args:
|
|
152
|
-
bounds (Tuple[float, float, float, float, float, float]): A tuple containing
|
|
153
|
-
the minimum and maximum values for each dimension (xmin, xmax, ymin, ymax, zmin, zmax).
|
|
154
|
-
"""
|
|
155
|
-
self._bounds = bounds
|
|
156
|
-
|
|
157
|
-
@property
|
|
158
|
-
def shape(self):
|
|
159
|
-
return self.active_data_array.shape
|
|
160
|
-
|
|
161
|
-
@property
|
|
162
|
-
def active_data_array(self):
|
|
163
|
-
return self.data[self.active_data_array_name]
|
|
164
|
-
|
|
165
|
-
@staticmethod
|
|
166
|
-
def _get_bounds_from_coord(xr_obj: xr.DataArray):
|
|
167
|
-
bounds = {}
|
|
168
|
-
for coord in xr_obj.coords:
|
|
169
|
-
bounds[coord] = (xr_obj[coord].min().item(), xr_obj[coord].max().item())
|
|
170
|
-
return bounds
|
|
171
|
-
|
|
172
|
-
def default_data_array_to_binary_legacy(self, order: Literal["K", "A", "C", "F"] = 'F'):
|
|
173
|
-
bytearray_le = self._to_bytearray(order=order)
|
|
174
|
-
header = self._set_binary_header()
|
|
175
|
-
|
|
176
|
-
return bytearray_le, header
|
|
177
|
-
|
|
178
|
-
def to_binary(self, order: Literal["K", "A", "C", "F"] = 'F') -> bytes:
|
|
179
|
-
"""Converts the structured data to a binary file
|
|
180
|
-
|
|
181
|
-
Notes:
|
|
182
|
-
Only the active data array is converted to binary for now
|
|
183
|
-
"""
|
|
184
|
-
|
|
185
|
-
body_ = self._to_bytearray(order)
|
|
186
|
-
header = self._set_binary_header()
|
|
187
|
-
|
|
188
|
-
import json
|
|
189
|
-
header_json = json.dumps(header)
|
|
190
|
-
header_json_bytes = header_json.encode('utf-8')
|
|
191
|
-
header_json_length = len(header_json_bytes)
|
|
192
|
-
header_json_length_bytes = header_json_length.to_bytes(4, byteorder='little')
|
|
193
|
-
file = header_json_length_bytes + header_json_bytes + body_
|
|
194
|
-
return file
|
|
195
|
-
|
|
196
|
-
def _set_binary_header(self) -> Dict:
|
|
197
|
-
data_array = self.active_data_array
|
|
198
|
-
|
|
199
|
-
match self.type:
|
|
200
|
-
case StructuredDataType.REGULAR_AXIS_ALIGNED:
|
|
201
|
-
header = {
|
|
202
|
-
"data_shape": self.shape,
|
|
203
|
-
"bounds" : self.bounds,
|
|
204
|
-
"transform" : None,
|
|
205
|
-
"dtype" : self.dtype,
|
|
206
|
-
"data_name" : self.active_data_array_name
|
|
207
|
-
}
|
|
208
|
-
case _:
|
|
209
|
-
raise NotImplementedError(f"StructuredDataType {self.type} not implemented yet")
|
|
210
|
-
|
|
211
|
-
return header
|
|
212
|
-
|
|
213
|
-
def _to_bytearray(self, order: Literal["K", "A", "C", "F"]) -> bytes:
|
|
214
|
-
data_array = self.active_data_array
|
|
215
|
-
|
|
216
|
-
data = data_array.values.astype(self.dtype).tobytes(order)
|
|
217
|
-
bytearray_le = data
|
|
218
|
-
return bytearray_le
|
|
219
|
-
|
|
220
|
-
@classmethod
|
|
221
|
-
def _default_dim_names(cls, n_dims: int):
|
|
222
|
-
if n_dims == 2:
|
|
223
|
-
dim_names = ['x', 'y']
|
|
224
|
-
elif n_dims == 3:
|
|
225
|
-
dim_names = ['x', 'y', 'z']
|
|
226
|
-
else:
|
|
227
|
-
dim_names = ['dim' + str(i) for i in range(n_dims)]
|
|
228
|
-
return dim_names
|
|
229
|
-
|
|
230
|
-
def to_netcdf(self, path: str, **to_netcdf_kwargs):
|
|
231
|
-
"""
|
|
232
|
-
Serializes the current StructuredData instance to a NetCDF file.
|
|
233
|
-
|
|
234
|
-
Args:
|
|
235
|
-
path (str): The path (including file name) where the NetCDF file will be saved.
|
|
236
|
-
**to_netcdf_kwargs: Additional keyword arguments forwarded to xarray's `to_netcdf`.
|
|
237
|
-
"""
|
|
238
|
-
# Copy the dataset (shallow copy of the data structure, no copying of the underlying arrays)
|
|
239
|
-
ds = self.data.copy(deep=False)
|
|
240
|
-
|
|
241
|
-
# Store relevant metadata as global attributes:
|
|
242
|
-
ds.attrs["active_data_array_name"] = self._active_data_array_name
|
|
243
|
-
ds.attrs["structured_data_type"] = self.type.name # e.g., "REGULAR_AXIS_ALIGNED"
|
|
244
|
-
ds.attrs["dtype"] = self.dtype # e.g., "float32"
|
|
245
|
-
|
|
246
|
-
# Use xarray's to_netcdf
|
|
247
|
-
ds.to_netcdf(path, **to_netcdf_kwargs)
|
|
248
|
-
|
|
249
|
-
@classmethod
|
|
250
|
-
def from_netcdf(cls, path: str, **from_netcdf_kwargs):
|
|
251
|
-
"""
|
|
252
|
-
Deserializes a NetCDF file into a StructuredData instance.
|
|
253
|
-
|
|
254
|
-
Args:
|
|
255
|
-
path (str): The path to the NetCDF file to read.
|
|
256
|
-
**from_netcdf_kwargs: Additional keyword arguments forwarded to xarray's `open_dataset`.
|
|
257
|
-
|
|
258
|
-
Returns:
|
|
259
|
-
StructuredData: A new instance of StructuredData loaded from the file.
|
|
260
|
-
"""
|
|
261
|
-
ds = xr.open_dataset(path, **from_netcdf_kwargs)
|
|
262
|
-
|
|
263
|
-
# Retrieve what was stored in attrs (with defaults if missing)
|
|
264
|
-
data_array_name = ds.attrs.get("active_data_array_name", "data_array")
|
|
265
|
-
dtype_str: str = ds.attrs.get("dtype", "float32")
|
|
266
|
-
if dtype_str not in ["float32", "float64"]:
|
|
267
|
-
raise ValueError(f"Unsupported dtype: {dtype_str}")
|
|
268
|
-
|
|
269
|
-
sdt_str = ds.attrs.get("structured_data_type", "REGULAR_AXIS_ALIGNED")
|
|
270
|
-
|
|
271
|
-
# Convert strings back to your enum or any other type
|
|
272
|
-
# (assuming StructuredDataType is an Enum where name matches sdt_str)
|
|
273
|
-
if sdt_str not in StructuredDataType.__members__:
|
|
274
|
-
raise ValueError(f"Unsupported structured_data_type: {sdt_str}")
|
|
275
|
-
structured_data_type: StructuredDataType = StructuredDataType[sdt_str]
|
|
276
|
-
|
|
277
|
-
return cls(
|
|
278
|
-
data=ds,
|
|
279
|
-
_active_data_array_name=data_array_name,
|
|
280
|
-
type=structured_data_type,
|
|
281
|
-
dtype=dtype_str
|
|
282
|
-
)
|
|
1
|
+
import enum
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Dict, List, Tuple, Union, Literal
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import xarray as xr
|
|
7
|
+
|
|
8
|
+
from ....optional_requirements import require_pyvista
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StructuredDataType(enum.Enum):
|
|
12
|
+
REGULAR_AXIS_ALIGNED = 0 #: Regular axis aligned grid. Distance between consecutive points is constant
|
|
13
|
+
REGULAR_AXIS_UNALIGNED = 1 #: Regular axis unaligned grid. Distance between consecutive points is constant
|
|
14
|
+
IRREGULAR_AXIS_ALIGNED = 2 #: Irregular axis aligned grid. Distance between consecutive points is not constant
|
|
15
|
+
IRREGULAR_AXIS_UNALIGNED = 3 #: Irregular axis unaligned grid. Distance between consecutive points is not constant
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=False)
|
|
19
|
+
class StructuredData:
|
|
20
|
+
data: xr.Dataset
|
|
21
|
+
_active_data_array_name: str = "data_array"
|
|
22
|
+
type: StructuredDataType = StructuredDataType.REGULAR_AXIS_ALIGNED
|
|
23
|
+
dtype: Literal["float32", "float64"] = "float32"
|
|
24
|
+
|
|
25
|
+
"""Primary structure definition for structured data
|
|
26
|
+
|
|
27
|
+
Check out other constructors: `StructuredData.from_numpy`,
|
|
28
|
+
`StructuredData.from_data_array` and `StructuredData.from_dict`
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
data (xr.Dataset): object containing
|
|
32
|
+
structured data, i.e. data that can be stored in multidimensional
|
|
33
|
+
numpy array. The preferred type to pass as data is directly a
|
|
34
|
+
xr.Dataset to be sure all the attributes are set and named as the user
|
|
35
|
+
wants.
|
|
36
|
+
data_array_name (str): If data is a numpy array or xarray DataArray, data_name
|
|
37
|
+
provides the name for the xarray data variable
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
data (xarray.Dataset)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def active_data_array_name(self):
|
|
45
|
+
data_var_list = list(self.data.data_vars.keys())
|
|
46
|
+
if self._active_data_array_name not in data_var_list:
|
|
47
|
+
raise ValueError("data_array_name not found in data_vars: {}".format(data_var_list))
|
|
48
|
+
return self._active_data_array_name
|
|
49
|
+
|
|
50
|
+
@active_data_array_name.setter
|
|
51
|
+
def active_data_array_name(self, data_array_name: str):
|
|
52
|
+
self._active_data_array_name = data_array_name
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_numpy(cls, array: np.ndarray, coords: dict = None, data_array_name: str = "data_array",
|
|
56
|
+
dim_names: List[str] = None):
|
|
57
|
+
if dim_names is None:
|
|
58
|
+
dim_names = cls._default_dim_names(array.ndim)
|
|
59
|
+
# if they are more than 3 we do not know the dimension name but it should valid:
|
|
60
|
+
|
|
61
|
+
dataset: xr.Dataset = xr.Dataset(
|
|
62
|
+
data_vars=
|
|
63
|
+
{
|
|
64
|
+
data_array_name: (dim_names, array)
|
|
65
|
+
},
|
|
66
|
+
coords=coords
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return cls(dataset, data_array_name)
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_data_array(cls, data_array: xr.DataArray, data_array_name: str = "data_array"):
|
|
73
|
+
dataset: xr.Dataset = xr.Dataset(
|
|
74
|
+
data_vars={
|
|
75
|
+
data_array_name: data_array
|
|
76
|
+
},
|
|
77
|
+
coords=data_array.coords
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return cls(dataset, data_array_name)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_dict(cls, data_dict: Dict[str, xr.DataArray], coords: Dict[str, str] = None, data_array_name: str = "data_array"):
|
|
84
|
+
dataset: xr.Dataset = xr.Dataset(data_vars=data_dict, coords=coords)
|
|
85
|
+
return cls(dataset, data_array_name)
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def from_pyvista_structured_grid(
|
|
89
|
+
cls,
|
|
90
|
+
grid: Union["pyvista.ExplicitStructuredGrid", "pyvista.StructuredGrid"],
|
|
91
|
+
data_array_name: str = "data_array"
|
|
92
|
+
):
|
|
93
|
+
pyvista = require_pyvista()
|
|
94
|
+
# Extract p
|
|
95
|
+
|
|
96
|
+
# Extract cell data and point data (if any)
|
|
97
|
+
data_vars = {}
|
|
98
|
+
|
|
99
|
+
# TODO: I need to do something with the bounds
|
|
100
|
+
|
|
101
|
+
dimensions = np.array(grid.dimensions) - 1
|
|
102
|
+
default_dim_names = cls._default_dim_names(dimensions.shape[0])
|
|
103
|
+
|
|
104
|
+
bounds: tuple = grid.bounds
|
|
105
|
+
coords = {}
|
|
106
|
+
for i, dim in enumerate(default_dim_names):
|
|
107
|
+
coords[dim] = np.linspace(
|
|
108
|
+
start=bounds[i * 2],
|
|
109
|
+
stop=bounds[i * 2 + 1],
|
|
110
|
+
num=dimensions[i],
|
|
111
|
+
endpoint=False
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
for name in grid.cell_data:
|
|
115
|
+
cell_attr_data: pyvista.pyvista_ndarray = grid[name]
|
|
116
|
+
cell_attr_data_reshaped = cell_attr_data.reshape(dimensions, order='F')
|
|
117
|
+
|
|
118
|
+
data_vars[name] = xr.DataArray(
|
|
119
|
+
data=cell_attr_data_reshaped,
|
|
120
|
+
dims=default_dim_names,
|
|
121
|
+
name=name
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
dataset: xr.Dataset = xr.Dataset(
|
|
125
|
+
data_vars=data_vars,
|
|
126
|
+
coords=coords
|
|
127
|
+
)
|
|
128
|
+
return cls(dataset, data_array_name)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def values(self) -> np.ndarray:
|
|
132
|
+
return self.data[self.active_data_array_name].values
|
|
133
|
+
|
|
134
|
+
_bounds: Tuple[float, float, float, float, float, float] = None
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def bounds(self):
|
|
138
|
+
if self._bounds is not None:
|
|
139
|
+
return self._bounds
|
|
140
|
+
|
|
141
|
+
array_: xr.DataArray = self.data[self.active_data_array_name]
|
|
142
|
+
bounds = self._get_bounds_from_coord(array_)
|
|
143
|
+
return bounds
|
|
144
|
+
|
|
145
|
+
@bounds.setter
|
|
146
|
+
def bounds(self, bounds: Tuple[float, float, float, float, float, float]):
|
|
147
|
+
"""
|
|
148
|
+
Set the bounds of the structured data. This is useful for defining the
|
|
149
|
+
spatial extent of the data in a structured grid.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
bounds (Tuple[float, float, float, float, float, float]): A tuple containing
|
|
153
|
+
the minimum and maximum values for each dimension (xmin, xmax, ymin, ymax, zmin, zmax).
|
|
154
|
+
"""
|
|
155
|
+
self._bounds = bounds
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def shape(self):
|
|
159
|
+
return self.active_data_array.shape
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def active_data_array(self):
|
|
163
|
+
return self.data[self.active_data_array_name]
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def _get_bounds_from_coord(xr_obj: xr.DataArray):
|
|
167
|
+
bounds = {}
|
|
168
|
+
for coord in xr_obj.coords:
|
|
169
|
+
bounds[coord] = (xr_obj[coord].min().item(), xr_obj[coord].max().item())
|
|
170
|
+
return bounds
|
|
171
|
+
|
|
172
|
+
def default_data_array_to_binary_legacy(self, order: Literal["K", "A", "C", "F"] = 'F'):
|
|
173
|
+
bytearray_le = self._to_bytearray(order=order)
|
|
174
|
+
header = self._set_binary_header()
|
|
175
|
+
|
|
176
|
+
return bytearray_le, header
|
|
177
|
+
|
|
178
|
+
def to_binary(self, order: Literal["K", "A", "C", "F"] = 'F') -> bytes:
|
|
179
|
+
"""Converts the structured data to a binary file
|
|
180
|
+
|
|
181
|
+
Notes:
|
|
182
|
+
Only the active data array is converted to binary for now
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
body_ = self._to_bytearray(order)
|
|
186
|
+
header = self._set_binary_header()
|
|
187
|
+
|
|
188
|
+
import json
|
|
189
|
+
header_json = json.dumps(header)
|
|
190
|
+
header_json_bytes = header_json.encode('utf-8')
|
|
191
|
+
header_json_length = len(header_json_bytes)
|
|
192
|
+
header_json_length_bytes = header_json_length.to_bytes(4, byteorder='little')
|
|
193
|
+
file = header_json_length_bytes + header_json_bytes + body_
|
|
194
|
+
return file
|
|
195
|
+
|
|
196
|
+
def _set_binary_header(self) -> Dict:
|
|
197
|
+
data_array = self.active_data_array
|
|
198
|
+
|
|
199
|
+
match self.type:
|
|
200
|
+
case StructuredDataType.REGULAR_AXIS_ALIGNED:
|
|
201
|
+
header = {
|
|
202
|
+
"data_shape": self.shape,
|
|
203
|
+
"bounds" : self.bounds,
|
|
204
|
+
"transform" : None,
|
|
205
|
+
"dtype" : self.dtype,
|
|
206
|
+
"data_name" : self.active_data_array_name
|
|
207
|
+
}
|
|
208
|
+
case _:
|
|
209
|
+
raise NotImplementedError(f"StructuredDataType {self.type} not implemented yet")
|
|
210
|
+
|
|
211
|
+
return header
|
|
212
|
+
|
|
213
|
+
def _to_bytearray(self, order: Literal["K", "A", "C", "F"]) -> bytes:
|
|
214
|
+
data_array = self.active_data_array
|
|
215
|
+
|
|
216
|
+
data = data_array.values.astype(self.dtype).tobytes(order)
|
|
217
|
+
bytearray_le = data
|
|
218
|
+
return bytearray_le
|
|
219
|
+
|
|
220
|
+
@classmethod
|
|
221
|
+
def _default_dim_names(cls, n_dims: int):
|
|
222
|
+
if n_dims == 2:
|
|
223
|
+
dim_names = ['x', 'y']
|
|
224
|
+
elif n_dims == 3:
|
|
225
|
+
dim_names = ['x', 'y', 'z']
|
|
226
|
+
else:
|
|
227
|
+
dim_names = ['dim' + str(i) for i in range(n_dims)]
|
|
228
|
+
return dim_names
|
|
229
|
+
|
|
230
|
+
def to_netcdf(self, path: str, **to_netcdf_kwargs):
|
|
231
|
+
"""
|
|
232
|
+
Serializes the current StructuredData instance to a NetCDF file.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
path (str): The path (including file name) where the NetCDF file will be saved.
|
|
236
|
+
**to_netcdf_kwargs: Additional keyword arguments forwarded to xarray's `to_netcdf`.
|
|
237
|
+
"""
|
|
238
|
+
# Copy the dataset (shallow copy of the data structure, no copying of the underlying arrays)
|
|
239
|
+
ds = self.data.copy(deep=False)
|
|
240
|
+
|
|
241
|
+
# Store relevant metadata as global attributes:
|
|
242
|
+
ds.attrs["active_data_array_name"] = self._active_data_array_name
|
|
243
|
+
ds.attrs["structured_data_type"] = self.type.name # e.g., "REGULAR_AXIS_ALIGNED"
|
|
244
|
+
ds.attrs["dtype"] = self.dtype # e.g., "float32"
|
|
245
|
+
|
|
246
|
+
# Use xarray's to_netcdf
|
|
247
|
+
ds.to_netcdf(path, **to_netcdf_kwargs)
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def from_netcdf(cls, path: str, **from_netcdf_kwargs):
|
|
251
|
+
"""
|
|
252
|
+
Deserializes a NetCDF file into a StructuredData instance.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
path (str): The path to the NetCDF file to read.
|
|
256
|
+
**from_netcdf_kwargs: Additional keyword arguments forwarded to xarray's `open_dataset`.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
StructuredData: A new instance of StructuredData loaded from the file.
|
|
260
|
+
"""
|
|
261
|
+
ds = xr.open_dataset(path, **from_netcdf_kwargs)
|
|
262
|
+
|
|
263
|
+
# Retrieve what was stored in attrs (with defaults if missing)
|
|
264
|
+
data_array_name = ds.attrs.get("active_data_array_name", "data_array")
|
|
265
|
+
dtype_str: str = ds.attrs.get("dtype", "float32")
|
|
266
|
+
if dtype_str not in ["float32", "float64"]:
|
|
267
|
+
raise ValueError(f"Unsupported dtype: {dtype_str}")
|
|
268
|
+
|
|
269
|
+
sdt_str = ds.attrs.get("structured_data_type", "REGULAR_AXIS_ALIGNED")
|
|
270
|
+
|
|
271
|
+
# Convert strings back to your enum or any other type
|
|
272
|
+
# (assuming StructuredDataType is an Enum where name matches sdt_str)
|
|
273
|
+
if sdt_str not in StructuredDataType.__members__:
|
|
274
|
+
raise ValueError(f"Unsupported structured_data_type: {sdt_str}")
|
|
275
|
+
structured_data_type: StructuredDataType = StructuredDataType[sdt_str]
|
|
276
|
+
|
|
277
|
+
return cls(
|
|
278
|
+
data=ds,
|
|
279
|
+
_active_data_array_name=data_array_name,
|
|
280
|
+
type=structured_data_type,
|
|
281
|
+
dtype=dtype_str
|
|
282
|
+
)
|