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,163 +1,163 @@
|
|
|
1
|
-
from typing import Hashable, Optional
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
import pandas as pd
|
|
5
|
-
|
|
6
|
-
from subsurface import optional_requirements
|
|
7
|
-
from ...structs.base_structures import UnstructuredData
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def data_frame_to_unstructured_data(survey_df: 'pd.DataFrame', number_nodes: int, attr_df: Optional['pd.DataFrame'] = None,
|
|
11
|
-
duplicate_attr_depths: bool = False) -> UnstructuredData:
|
|
12
|
-
wp = optional_requirements.require_wellpathpy()
|
|
13
|
-
|
|
14
|
-
cum_vertex: np.ndarray = np.empty((0, 3), dtype=np.float32)
|
|
15
|
-
cells: np.ndarray = np.empty((0, 2), dtype=np.int_)
|
|
16
|
-
cell_attr: pd.DataFrame = pd.DataFrame(columns=['well_id'], dtype=np.float32)
|
|
17
|
-
vertex_attr: pd.DataFrame = pd.DataFrame()
|
|
18
|
-
|
|
19
|
-
for e, (borehole_id, data) in enumerate(survey_df.groupby(level=0)):
|
|
20
|
-
dev = wp.deviation(
|
|
21
|
-
md=data['md'].values,
|
|
22
|
-
inc=data['inc'].values,
|
|
23
|
-
azi=data['azi'].values
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
md_min = dev.md.min()
|
|
27
|
-
md_max = dev.md.max()
|
|
28
|
-
|
|
29
|
-
attr_depths = _grab_depths_from_attr(
|
|
30
|
-
attr_df=attr_df,
|
|
31
|
-
borehole_id=borehole_id,
|
|
32
|
-
duplicate_attr_depths=duplicate_attr_depths,
|
|
33
|
-
md_max=md_max,
|
|
34
|
-
md_min=md_min
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
# Now combine attr_depths with depths
|
|
38
|
-
md_min = dev.md.min()
|
|
39
|
-
md_max = dev.md.max()
|
|
40
|
-
depths = np.linspace(md_min, md_max, number_nodes)
|
|
41
|
-
depths = np.union1d(depths, attr_depths)
|
|
42
|
-
depths.sort()
|
|
43
|
-
|
|
44
|
-
# Resample positions at depths
|
|
45
|
-
pos = dev.minimum_curvature().resample(depths=depths)
|
|
46
|
-
vertex_count = cum_vertex.shape[0]
|
|
47
|
-
|
|
48
|
-
this_well_vertex = np.vstack([pos.easting, pos.northing, pos.depth]).T
|
|
49
|
-
cum_vertex = np.vstack([cum_vertex, this_well_vertex])
|
|
50
|
-
measured_depths = _calculate_distances(array_of_vertices=this_well_vertex)
|
|
51
|
-
|
|
52
|
-
n_vertex_shift_0 = np.arange(0, len(pos.depth) - 1, dtype=np.int_)
|
|
53
|
-
n_vertex_shift_1 = np.arange(1, len(pos.depth), dtype=np.int_)
|
|
54
|
-
cell_per_well = np.vstack([n_vertex_shift_0, n_vertex_shift_1]).T + vertex_count
|
|
55
|
-
cells = np.vstack([cells, cell_per_well])
|
|
56
|
-
|
|
57
|
-
attribute_values = np.isin(depths, attr_depths)
|
|
58
|
-
|
|
59
|
-
vertex_attr_per_well = pd.DataFrame({
|
|
60
|
-
'well_id' : [e] * len(pos.depth),
|
|
61
|
-
'measured_depths': measured_depths,
|
|
62
|
-
'is_attr_point' : attribute_values,
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
vertex_attr = pd.concat([vertex_attr, vertex_attr_per_well], ignore_index=True)
|
|
66
|
-
|
|
67
|
-
# Add the id (e), to cell_attr
|
|
68
|
-
cell_attr = pd.concat([cell_attr, pd.DataFrame({'well_id': [e] * len(cell_per_well)})], ignore_index=True)
|
|
69
|
-
|
|
70
|
-
unstruct = UnstructuredData.from_array(
|
|
71
|
-
vertex=cum_vertex,
|
|
72
|
-
cells=cells.astype(int),
|
|
73
|
-
vertex_attr=vertex_attr.reset_index(drop=True),
|
|
74
|
-
cells_attr=cell_attr.reset_index(drop=True)
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
unstruct.data.attrs["well_id_mapper"] = {well_id: e for e, well_id in enumerate(survey_df.index.unique(level=0))}
|
|
78
|
-
|
|
79
|
-
return unstruct
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _grab_depths_from_attr(
|
|
83
|
-
attr_df: pd.DataFrame,
|
|
84
|
-
borehole_id: Hashable,
|
|
85
|
-
duplicate_attr_depths: bool,
|
|
86
|
-
md_max: float,
|
|
87
|
-
md_min: float
|
|
88
|
-
) -> np.ndarray:
|
|
89
|
-
# Initialize attr_depths and attr_labels as empty arrays
|
|
90
|
-
attr_depths = np.array([], dtype=float)
|
|
91
|
-
attr_labels = np.array([], dtype='<U4') # Initialize labels for 'top' and 'base'
|
|
92
|
-
|
|
93
|
-
if attr_df is None or ("top" not in attr_df.columns and "base" not in attr_df.columns):
|
|
94
|
-
return attr_depths
|
|
95
|
-
|
|
96
|
-
try:
|
|
97
|
-
vals = attr_df.loc[borehole_id]
|
|
98
|
-
|
|
99
|
-
tops = np.array([], dtype=float)
|
|
100
|
-
bases = np.array([], dtype=float)
|
|
101
|
-
|
|
102
|
-
if 'top' in vals:
|
|
103
|
-
if isinstance(vals, pd.DataFrame):
|
|
104
|
-
tops = vals['top'].values.flatten()
|
|
105
|
-
else:
|
|
106
|
-
tops = np.array([vals['top']])
|
|
107
|
-
# Convert to float and remove NaNs
|
|
108
|
-
tops = tops.astype(float)
|
|
109
|
-
tops = tops[~np.isnan(tops)]
|
|
110
|
-
# Clip to within md range
|
|
111
|
-
tops = tops[(tops >= md_min) & (tops <= md_max)]
|
|
112
|
-
|
|
113
|
-
if 'base' in vals:
|
|
114
|
-
if isinstance(vals, pd.DataFrame):
|
|
115
|
-
bases = vals['base'].values.flatten()
|
|
116
|
-
else:
|
|
117
|
-
bases = np.array([vals['base']])
|
|
118
|
-
# Convert to float and remove NaNs
|
|
119
|
-
bases = bases.astype(float)
|
|
120
|
-
bases = bases[~np.isnan(bases)]
|
|
121
|
-
# Clip to within md range
|
|
122
|
-
bases = bases[(bases >= md_min) & (bases <= md_max)]
|
|
123
|
-
|
|
124
|
-
# Combine tops and bases into attr_depths with labels
|
|
125
|
-
attr_depths = np.concatenate((tops, bases))
|
|
126
|
-
attr_labels = np.array(['top'] * len(tops) + ['base'] * len(bases))
|
|
127
|
-
|
|
128
|
-
# Drop duplicates while preserving order
|
|
129
|
-
_, unique_indices = np.unique(attr_depths, return_index=True)
|
|
130
|
-
attr_depths = attr_depths[unique_indices]
|
|
131
|
-
attr_labels = attr_labels[unique_indices]
|
|
132
|
-
|
|
133
|
-
except KeyError:
|
|
134
|
-
# No attributes for this borehole_id or missing columns
|
|
135
|
-
attr_depths = np.array([], dtype=float)
|
|
136
|
-
attr_labels = np.array([], dtype='<U4')
|
|
137
|
-
|
|
138
|
-
# If duplicate_attr_depths is True, duplicate attr_depths with a tiny offset
|
|
139
|
-
if duplicate_attr_depths and len(attr_depths) > 0:
|
|
140
|
-
tiny_offset = (md_max - md_min) * 1e-6 # A tiny fraction of the depth range
|
|
141
|
-
# Create offsets: +tiny_offset for 'top', -tiny_offset for 'base'
|
|
142
|
-
offsets = np.where(attr_labels == 'top', tiny_offset, -tiny_offset)
|
|
143
|
-
duplicated_attr_depths = attr_depths + offsets
|
|
144
|
-
# Ensure the duplicated depths are within the md range
|
|
145
|
-
valid_indices = (duplicated_attr_depths >= md_min) & (duplicated_attr_depths <= md_max)
|
|
146
|
-
duplicated_attr_depths = duplicated_attr_depths[valid_indices]
|
|
147
|
-
# Original attribute depths
|
|
148
|
-
original_attr_depths = attr_depths
|
|
149
|
-
# Combine originals and duplicates
|
|
150
|
-
attr_depths = np.hstack([original_attr_depths, duplicated_attr_depths])
|
|
151
|
-
|
|
152
|
-
return attr_depths
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def _calculate_distances(array_of_vertices: np.ndarray) -> np.ndarray:
|
|
156
|
-
# Calculate the differences between consecutive points
|
|
157
|
-
differences = np.diff(array_of_vertices, axis=0)
|
|
158
|
-
|
|
159
|
-
# Calculate the Euclidean distance for each pair of consecutive points
|
|
160
|
-
distances = np.linalg.norm(differences, axis=1)
|
|
161
|
-
# Insert a 0 at the beginning to represent the starting point at the surface
|
|
162
|
-
measured_depths = np.insert(np.cumsum(distances), 0, 0)
|
|
163
|
-
return measured_depths
|
|
1
|
+
from typing import Hashable, Optional
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from subsurface import optional_requirements
|
|
7
|
+
from ...structs.base_structures import UnstructuredData
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def data_frame_to_unstructured_data(survey_df: 'pd.DataFrame', number_nodes: int, attr_df: Optional['pd.DataFrame'] = None,
|
|
11
|
+
duplicate_attr_depths: bool = False) -> UnstructuredData:
|
|
12
|
+
wp = optional_requirements.require_wellpathpy()
|
|
13
|
+
|
|
14
|
+
cum_vertex: np.ndarray = np.empty((0, 3), dtype=np.float32)
|
|
15
|
+
cells: np.ndarray = np.empty((0, 2), dtype=np.int_)
|
|
16
|
+
cell_attr: pd.DataFrame = pd.DataFrame(columns=['well_id'], dtype=np.float32)
|
|
17
|
+
vertex_attr: pd.DataFrame = pd.DataFrame()
|
|
18
|
+
|
|
19
|
+
for e, (borehole_id, data) in enumerate(survey_df.groupby(level=0)):
|
|
20
|
+
dev = wp.deviation(
|
|
21
|
+
md=data['md'].values,
|
|
22
|
+
inc=data['inc'].values,
|
|
23
|
+
azi=data['azi'].values
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
md_min = dev.md.min()
|
|
27
|
+
md_max = dev.md.max()
|
|
28
|
+
|
|
29
|
+
attr_depths = _grab_depths_from_attr(
|
|
30
|
+
attr_df=attr_df,
|
|
31
|
+
borehole_id=borehole_id,
|
|
32
|
+
duplicate_attr_depths=duplicate_attr_depths,
|
|
33
|
+
md_max=md_max,
|
|
34
|
+
md_min=md_min
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Now combine attr_depths with depths
|
|
38
|
+
md_min = dev.md.min()
|
|
39
|
+
md_max = dev.md.max()
|
|
40
|
+
depths = np.linspace(md_min, md_max, number_nodes)
|
|
41
|
+
depths = np.union1d(depths, attr_depths)
|
|
42
|
+
depths.sort()
|
|
43
|
+
|
|
44
|
+
# Resample positions at depths
|
|
45
|
+
pos = dev.minimum_curvature().resample(depths=depths)
|
|
46
|
+
vertex_count = cum_vertex.shape[0]
|
|
47
|
+
|
|
48
|
+
this_well_vertex = np.vstack([pos.easting, pos.northing, pos.depth]).T
|
|
49
|
+
cum_vertex = np.vstack([cum_vertex, this_well_vertex])
|
|
50
|
+
measured_depths = _calculate_distances(array_of_vertices=this_well_vertex)
|
|
51
|
+
|
|
52
|
+
n_vertex_shift_0 = np.arange(0, len(pos.depth) - 1, dtype=np.int_)
|
|
53
|
+
n_vertex_shift_1 = np.arange(1, len(pos.depth), dtype=np.int_)
|
|
54
|
+
cell_per_well = np.vstack([n_vertex_shift_0, n_vertex_shift_1]).T + vertex_count
|
|
55
|
+
cells = np.vstack([cells, cell_per_well])
|
|
56
|
+
|
|
57
|
+
attribute_values = np.isin(depths, attr_depths)
|
|
58
|
+
|
|
59
|
+
vertex_attr_per_well = pd.DataFrame({
|
|
60
|
+
'well_id' : [e] * len(pos.depth),
|
|
61
|
+
'measured_depths': measured_depths,
|
|
62
|
+
'is_attr_point' : attribute_values,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
vertex_attr = pd.concat([vertex_attr, vertex_attr_per_well], ignore_index=True)
|
|
66
|
+
|
|
67
|
+
# Add the id (e), to cell_attr
|
|
68
|
+
cell_attr = pd.concat([cell_attr, pd.DataFrame({'well_id': [e] * len(cell_per_well)})], ignore_index=True)
|
|
69
|
+
|
|
70
|
+
unstruct = UnstructuredData.from_array(
|
|
71
|
+
vertex=cum_vertex,
|
|
72
|
+
cells=cells.astype(int),
|
|
73
|
+
vertex_attr=vertex_attr.reset_index(drop=True),
|
|
74
|
+
cells_attr=cell_attr.reset_index(drop=True)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
unstruct.data.attrs["well_id_mapper"] = {well_id: e for e, well_id in enumerate(survey_df.index.unique(level=0))}
|
|
78
|
+
|
|
79
|
+
return unstruct
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _grab_depths_from_attr(
|
|
83
|
+
attr_df: pd.DataFrame,
|
|
84
|
+
borehole_id: Hashable,
|
|
85
|
+
duplicate_attr_depths: bool,
|
|
86
|
+
md_max: float,
|
|
87
|
+
md_min: float
|
|
88
|
+
) -> np.ndarray:
|
|
89
|
+
# Initialize attr_depths and attr_labels as empty arrays
|
|
90
|
+
attr_depths = np.array([], dtype=float)
|
|
91
|
+
attr_labels = np.array([], dtype='<U4') # Initialize labels for 'top' and 'base'
|
|
92
|
+
|
|
93
|
+
if attr_df is None or ("top" not in attr_df.columns and "base" not in attr_df.columns):
|
|
94
|
+
return attr_depths
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
vals = attr_df.loc[borehole_id]
|
|
98
|
+
|
|
99
|
+
tops = np.array([], dtype=float)
|
|
100
|
+
bases = np.array([], dtype=float)
|
|
101
|
+
|
|
102
|
+
if 'top' in vals:
|
|
103
|
+
if isinstance(vals, pd.DataFrame):
|
|
104
|
+
tops = vals['top'].values.flatten()
|
|
105
|
+
else:
|
|
106
|
+
tops = np.array([vals['top']])
|
|
107
|
+
# Convert to float and remove NaNs
|
|
108
|
+
tops = tops.astype(float)
|
|
109
|
+
tops = tops[~np.isnan(tops)]
|
|
110
|
+
# Clip to within md range
|
|
111
|
+
tops = tops[(tops >= md_min) & (tops <= md_max)]
|
|
112
|
+
|
|
113
|
+
if 'base' in vals:
|
|
114
|
+
if isinstance(vals, pd.DataFrame):
|
|
115
|
+
bases = vals['base'].values.flatten()
|
|
116
|
+
else:
|
|
117
|
+
bases = np.array([vals['base']])
|
|
118
|
+
# Convert to float and remove NaNs
|
|
119
|
+
bases = bases.astype(float)
|
|
120
|
+
bases = bases[~np.isnan(bases)]
|
|
121
|
+
# Clip to within md range
|
|
122
|
+
bases = bases[(bases >= md_min) & (bases <= md_max)]
|
|
123
|
+
|
|
124
|
+
# Combine tops and bases into attr_depths with labels
|
|
125
|
+
attr_depths = np.concatenate((tops, bases))
|
|
126
|
+
attr_labels = np.array(['top'] * len(tops) + ['base'] * len(bases))
|
|
127
|
+
|
|
128
|
+
# Drop duplicates while preserving order
|
|
129
|
+
_, unique_indices = np.unique(attr_depths, return_index=True)
|
|
130
|
+
attr_depths = attr_depths[unique_indices]
|
|
131
|
+
attr_labels = attr_labels[unique_indices]
|
|
132
|
+
|
|
133
|
+
except KeyError:
|
|
134
|
+
# No attributes for this borehole_id or missing columns
|
|
135
|
+
attr_depths = np.array([], dtype=float)
|
|
136
|
+
attr_labels = np.array([], dtype='<U4')
|
|
137
|
+
|
|
138
|
+
# If duplicate_attr_depths is True, duplicate attr_depths with a tiny offset
|
|
139
|
+
if duplicate_attr_depths and len(attr_depths) > 0:
|
|
140
|
+
tiny_offset = (md_max - md_min) * 1e-6 # A tiny fraction of the depth range
|
|
141
|
+
# Create offsets: +tiny_offset for 'top', -tiny_offset for 'base'
|
|
142
|
+
offsets = np.where(attr_labels == 'top', tiny_offset, -tiny_offset)
|
|
143
|
+
duplicated_attr_depths = attr_depths + offsets
|
|
144
|
+
# Ensure the duplicated depths are within the md range
|
|
145
|
+
valid_indices = (duplicated_attr_depths >= md_min) & (duplicated_attr_depths <= md_max)
|
|
146
|
+
duplicated_attr_depths = duplicated_attr_depths[valid_indices]
|
|
147
|
+
# Original attribute depths
|
|
148
|
+
original_attr_depths = attr_depths
|
|
149
|
+
# Combine originals and duplicates
|
|
150
|
+
attr_depths = np.hstack([original_attr_depths, duplicated_attr_depths])
|
|
151
|
+
|
|
152
|
+
return attr_depths
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _calculate_distances(array_of_vertices: np.ndarray) -> np.ndarray:
|
|
156
|
+
# Calculate the differences between consecutive points
|
|
157
|
+
differences = np.diff(array_of_vertices, axis=0)
|
|
158
|
+
|
|
159
|
+
# Calculate the Euclidean distance for each pair of consecutive points
|
|
160
|
+
distances = np.linalg.norm(differences, axis=1)
|
|
161
|
+
# Insert a 0 at the beginning to represent the starting point at the surface
|
|
162
|
+
measured_depths = np.insert(np.cumsum(distances), 0, 0)
|
|
163
|
+
return measured_depths
|
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import pandas as pd
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import Hashable, Literal
|
|
5
|
-
|
|
6
|
-
from ._combine_trajectories import create_combined_trajectory, MergeOptions
|
|
7
|
-
from .collars import Collars
|
|
8
|
-
from .survey import Survey
|
|
9
|
-
from ...structs import LineSet
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclass
|
|
13
|
-
class BoreholeSet:
|
|
14
|
-
"""
|
|
15
|
-
This module provides a class, `BoreholeSet`, that represents a collection of boreholes. It contains methods for accessing coordinate data for each lithology in the boreholes.
|
|
16
|
-
|
|
17
|
-
Notes:
|
|
18
|
-
- Collars is defined as 1 UnstructuredData
|
|
19
|
-
- Combined trajectory is defined as 1 UnstructuredData
|
|
20
|
-
|
|
21
|
-
Classes:
|
|
22
|
-
- `BoreholeSet`: Represents a collection of boreholes.
|
|
23
|
-
|
|
24
|
-
Methods:
|
|
25
|
-
- `__init__`: Initializes a new `BoreholeSet` object with the specified input parameters.
|
|
26
|
-
- `get_top_coords_for_each_lith`: Returns a dictionary of top coordinates for each lithology in the boreholes.
|
|
27
|
-
- `get_bottom_coords_for_each_lith`: Returns a dictionary of bottom coordinates for each lithology in the boreholes.
|
|
28
|
-
|
|
29
|
-
Attributes:
|
|
30
|
-
- `collars`: A `Collars` object representing the collar information for the boreholes.
|
|
31
|
-
- `survey`: A `Survey` object representing the survey information for the boreholes.
|
|
32
|
-
- `combined_trajectory`: A `LineSet` object representing the combined trajectory of the boreholes.
|
|
33
|
-
|
|
34
|
-
Usage:
|
|
35
|
-
```
|
|
36
|
-
borehole_set = BoreholeSet(collars, survey, merge_option)
|
|
37
|
-
top_coords = borehole_set.get_top_coords_for_each_lith()
|
|
38
|
-
bottom_coords = borehole_set.get_bottom_coords_for_each_lith()
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Note: The example usage code provided above is for demonstration purposes only. Please replace `collars`, `survey`, and `merge_option` with the actual input parameters when using the `BoreholeSet` class.
|
|
42
|
-
|
|
43
|
-
"""
|
|
44
|
-
__slots__ = ['collars', 'survey', 'combined_trajectory']
|
|
45
|
-
collars: Collars
|
|
46
|
-
survey: Survey
|
|
47
|
-
combined_trajectory: LineSet
|
|
48
|
-
|
|
49
|
-
def __init__(self, collars: Collars, survey: Survey, merge_option: MergeOptions, slice_=slice(None)):
|
|
50
|
-
|
|
51
|
-
new_collars = self._remap_collars_with_survey(collars, survey)
|
|
52
|
-
|
|
53
|
-
self.collars = new_collars
|
|
54
|
-
self.survey = survey
|
|
55
|
-
self.combined_trajectory: LineSet = create_combined_trajectory(collars, survey, merge_option, slice_)
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def _remap_collars_with_survey(collars, survey):
|
|
59
|
-
import pandas as pd
|
|
60
|
-
# Create a DataFrame from your first list
|
|
61
|
-
df1 = pd.DataFrame({'name': collars.ids, 'x': collars.data.vertex[:, 0], 'y': collars.data.vertex[:, 1], 'z': collars.data.vertex[:, 2]})
|
|
62
|
-
df1 = df1.set_index('name')
|
|
63
|
-
# Reindex to match the second list of names
|
|
64
|
-
df_reindexed = df1.reindex(survey.well_id_mapper.keys())
|
|
65
|
-
new_collars = Collars.from_df(df_reindexed)
|
|
66
|
-
return new_collars
|
|
67
|
-
|
|
68
|
-
def to_binary(self, path: str) -> bool:
|
|
69
|
-
# I need to implement the survey to and then name the files accordingly
|
|
70
|
-
bytearray_le_collars: bytes = self.collars.data.to_binary()
|
|
71
|
-
bytearray_le_trajectory: bytes = self.combined_trajectory.data.to_binary()
|
|
72
|
-
|
|
73
|
-
new_file = open(f"{path}_collars.le", "wb")
|
|
74
|
-
new_file.write(bytearray_le_collars)
|
|
75
|
-
|
|
76
|
-
new_file = open(f"{path}_trajectory.le", "wb")
|
|
77
|
-
new_file.write(bytearray_le_trajectory)
|
|
78
|
-
return True
|
|
79
|
-
|
|
80
|
-
def get_top_coords_for_each_lith(self) -> dict[Hashable, np.ndarray]:
|
|
81
|
-
merged_df = self._merge_vertex_data_arrays_to_dataframe()
|
|
82
|
-
component_lith_arrays = {}
|
|
83
|
-
for lith, group in merged_df.groupby('lith_ids'):
|
|
84
|
-
lith = int(lith)
|
|
85
|
-
first_vertices = group.groupby('well_id').first().reset_index()
|
|
86
|
-
array = first_vertices[['X', 'Y', 'Z']].values
|
|
87
|
-
component_lith_arrays[lith] = array
|
|
88
|
-
|
|
89
|
-
return component_lith_arrays
|
|
90
|
-
|
|
91
|
-
def get_bottom_coords_for_each_lith(self, group_by: Literal['component lith', 'lith_ids'] = 'lith_ids') -> dict[Hashable, np.ndarray]:
|
|
92
|
-
"""
|
|
93
|
-
Retrieves the bottom coordinates for each lithological component or lith ID from
|
|
94
|
-
the merged vertex data arrays.
|
|
95
|
-
|
|
96
|
-
This function groups the merged data by either 'component lith' or 'lith_ids',
|
|
97
|
-
then extracts the coordinates of the bottommost vertices for each well. It
|
|
98
|
-
returns a dictionary where keys are either lithological component identifiers
|
|
99
|
-
or lith IDs, and values are arrays of 3D coordinates representing the bottom
|
|
100
|
-
vertices.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
group_by (Literal['component lith', 'lith_ids']): Specifies the grouping
|
|
104
|
-
column to use for lithological components. Acceptable values are either
|
|
105
|
-
'component lith' or 'lith_ids'. Defaults to 'lith_ids'.
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
dict[Hashable, np.ndarray]: A dictionary mapping the lithological component
|
|
109
|
-
or lith ID to an array of 3D coordinates ([X, Y, Z]) corresponding to the
|
|
110
|
-
bottom vertices for each well.
|
|
111
|
-
|
|
112
|
-
Raises:
|
|
113
|
-
ValueError: If no groups are found from the specified `group_by` column.
|
|
114
|
-
"""
|
|
115
|
-
merged_df = self._merge_vertex_data_arrays_to_dataframe()
|
|
116
|
-
component_lith_arrays = {}
|
|
117
|
-
group = merged_df.groupby(group_by)
|
|
118
|
-
|
|
119
|
-
if group.ngroups == 0:
|
|
120
|
-
raise ValueError("No components found")
|
|
121
|
-
for lith, group in group:
|
|
122
|
-
lith = int(lith)
|
|
123
|
-
first_vertices = group.groupby('well_id').last().reset_index()
|
|
124
|
-
array = first_vertices[['X', 'Y', 'Z']].values
|
|
125
|
-
component_lith_arrays[lith] = array
|
|
126
|
-
|
|
127
|
-
return component_lith_arrays
|
|
128
|
-
|
|
129
|
-
def _merge_vertex_data_arrays_to_dataframe(self):
|
|
130
|
-
ds = self.combined_trajectory.data.data
|
|
131
|
-
# Convert vertex attributes to a DataFrame for easier manipulation
|
|
132
|
-
vertex_attrs_df = ds['vertex_attrs'].to_dataframe().reset_index()
|
|
133
|
-
vertex_attrs_df = vertex_attrs_df.pivot(index='points', columns='vertex_attr', values='vertex_attrs').reset_index()
|
|
134
|
-
# Convert vertex coordinates to a DataFrame
|
|
135
|
-
vertex_df = ds['vertex'].to_dataframe().reset_index()
|
|
136
|
-
vertex_df = vertex_df.pivot(index='points', columns='XYZ', values='vertex').reset_index()
|
|
137
|
-
# Merge the attributes with the vertex coordinates
|
|
138
|
-
merged_df = pd.merge(vertex_df, vertex_attrs_df, on='points')
|
|
139
|
-
# Create a dictionary to hold the numpy arrays for each component lith
|
|
140
|
-
return merged_df
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Hashable, Literal
|
|
5
|
+
|
|
6
|
+
from ._combine_trajectories import create_combined_trajectory, MergeOptions
|
|
7
|
+
from .collars import Collars
|
|
8
|
+
from .survey import Survey
|
|
9
|
+
from ...structs import LineSet
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class BoreholeSet:
|
|
14
|
+
"""
|
|
15
|
+
This module provides a class, `BoreholeSet`, that represents a collection of boreholes. It contains methods for accessing coordinate data for each lithology in the boreholes.
|
|
16
|
+
|
|
17
|
+
Notes:
|
|
18
|
+
- Collars is defined as 1 UnstructuredData
|
|
19
|
+
- Combined trajectory is defined as 1 UnstructuredData
|
|
20
|
+
|
|
21
|
+
Classes:
|
|
22
|
+
- `BoreholeSet`: Represents a collection of boreholes.
|
|
23
|
+
|
|
24
|
+
Methods:
|
|
25
|
+
- `__init__`: Initializes a new `BoreholeSet` object with the specified input parameters.
|
|
26
|
+
- `get_top_coords_for_each_lith`: Returns a dictionary of top coordinates for each lithology in the boreholes.
|
|
27
|
+
- `get_bottom_coords_for_each_lith`: Returns a dictionary of bottom coordinates for each lithology in the boreholes.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
- `collars`: A `Collars` object representing the collar information for the boreholes.
|
|
31
|
+
- `survey`: A `Survey` object representing the survey information for the boreholes.
|
|
32
|
+
- `combined_trajectory`: A `LineSet` object representing the combined trajectory of the boreholes.
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
```
|
|
36
|
+
borehole_set = BoreholeSet(collars, survey, merge_option)
|
|
37
|
+
top_coords = borehole_set.get_top_coords_for_each_lith()
|
|
38
|
+
bottom_coords = borehole_set.get_bottom_coords_for_each_lith()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Note: The example usage code provided above is for demonstration purposes only. Please replace `collars`, `survey`, and `merge_option` with the actual input parameters when using the `BoreholeSet` class.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
__slots__ = ['collars', 'survey', 'combined_trajectory']
|
|
45
|
+
collars: Collars
|
|
46
|
+
survey: Survey
|
|
47
|
+
combined_trajectory: LineSet
|
|
48
|
+
|
|
49
|
+
def __init__(self, collars: Collars, survey: Survey, merge_option: MergeOptions, slice_=slice(None)):
|
|
50
|
+
|
|
51
|
+
new_collars = self._remap_collars_with_survey(collars, survey)
|
|
52
|
+
|
|
53
|
+
self.collars = new_collars
|
|
54
|
+
self.survey = survey
|
|
55
|
+
self.combined_trajectory: LineSet = create_combined_trajectory(collars, survey, merge_option, slice_)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _remap_collars_with_survey(collars, survey):
|
|
59
|
+
import pandas as pd
|
|
60
|
+
# Create a DataFrame from your first list
|
|
61
|
+
df1 = pd.DataFrame({'name': collars.ids, 'x': collars.data.vertex[:, 0], 'y': collars.data.vertex[:, 1], 'z': collars.data.vertex[:, 2]})
|
|
62
|
+
df1 = df1.set_index('name')
|
|
63
|
+
# Reindex to match the second list of names
|
|
64
|
+
df_reindexed = df1.reindex(survey.well_id_mapper.keys())
|
|
65
|
+
new_collars = Collars.from_df(df_reindexed)
|
|
66
|
+
return new_collars
|
|
67
|
+
|
|
68
|
+
def to_binary(self, path: str) -> bool:
|
|
69
|
+
# I need to implement the survey to and then name the files accordingly
|
|
70
|
+
bytearray_le_collars: bytes = self.collars.data.to_binary()
|
|
71
|
+
bytearray_le_trajectory: bytes = self.combined_trajectory.data.to_binary()
|
|
72
|
+
|
|
73
|
+
new_file = open(f"{path}_collars.le", "wb")
|
|
74
|
+
new_file.write(bytearray_le_collars)
|
|
75
|
+
|
|
76
|
+
new_file = open(f"{path}_trajectory.le", "wb")
|
|
77
|
+
new_file.write(bytearray_le_trajectory)
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
def get_top_coords_for_each_lith(self) -> dict[Hashable, np.ndarray]:
|
|
81
|
+
merged_df = self._merge_vertex_data_arrays_to_dataframe()
|
|
82
|
+
component_lith_arrays = {}
|
|
83
|
+
for lith, group in merged_df.groupby('lith_ids'):
|
|
84
|
+
lith = int(lith)
|
|
85
|
+
first_vertices = group.groupby('well_id').first().reset_index()
|
|
86
|
+
array = first_vertices[['X', 'Y', 'Z']].values
|
|
87
|
+
component_lith_arrays[lith] = array
|
|
88
|
+
|
|
89
|
+
return component_lith_arrays
|
|
90
|
+
|
|
91
|
+
def get_bottom_coords_for_each_lith(self, group_by: Literal['component lith', 'lith_ids'] = 'lith_ids') -> dict[Hashable, np.ndarray]:
|
|
92
|
+
"""
|
|
93
|
+
Retrieves the bottom coordinates for each lithological component or lith ID from
|
|
94
|
+
the merged vertex data arrays.
|
|
95
|
+
|
|
96
|
+
This function groups the merged data by either 'component lith' or 'lith_ids',
|
|
97
|
+
then extracts the coordinates of the bottommost vertices for each well. It
|
|
98
|
+
returns a dictionary where keys are either lithological component identifiers
|
|
99
|
+
or lith IDs, and values are arrays of 3D coordinates representing the bottom
|
|
100
|
+
vertices.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
group_by (Literal['component lith', 'lith_ids']): Specifies the grouping
|
|
104
|
+
column to use for lithological components. Acceptable values are either
|
|
105
|
+
'component lith' or 'lith_ids'. Defaults to 'lith_ids'.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
dict[Hashable, np.ndarray]: A dictionary mapping the lithological component
|
|
109
|
+
or lith ID to an array of 3D coordinates ([X, Y, Z]) corresponding to the
|
|
110
|
+
bottom vertices for each well.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError: If no groups are found from the specified `group_by` column.
|
|
114
|
+
"""
|
|
115
|
+
merged_df = self._merge_vertex_data_arrays_to_dataframe()
|
|
116
|
+
component_lith_arrays = {}
|
|
117
|
+
group = merged_df.groupby(group_by)
|
|
118
|
+
|
|
119
|
+
if group.ngroups == 0:
|
|
120
|
+
raise ValueError("No components found")
|
|
121
|
+
for lith, group in group:
|
|
122
|
+
lith = int(lith)
|
|
123
|
+
first_vertices = group.groupby('well_id').last().reset_index()
|
|
124
|
+
array = first_vertices[['X', 'Y', 'Z']].values
|
|
125
|
+
component_lith_arrays[lith] = array
|
|
126
|
+
|
|
127
|
+
return component_lith_arrays
|
|
128
|
+
|
|
129
|
+
def _merge_vertex_data_arrays_to_dataframe(self):
|
|
130
|
+
ds = self.combined_trajectory.data.data
|
|
131
|
+
# Convert vertex attributes to a DataFrame for easier manipulation
|
|
132
|
+
vertex_attrs_df = ds['vertex_attrs'].to_dataframe().reset_index()
|
|
133
|
+
vertex_attrs_df = vertex_attrs_df.pivot(index='points', columns='vertex_attr', values='vertex_attrs').reset_index()
|
|
134
|
+
# Convert vertex coordinates to a DataFrame
|
|
135
|
+
vertex_df = ds['vertex'].to_dataframe().reset_index()
|
|
136
|
+
vertex_df = vertex_df.pivot(index='points', columns='XYZ', values='vertex').reset_index()
|
|
137
|
+
# Merge the attributes with the vertex coordinates
|
|
138
|
+
merged_df = pd.merge(vertex_df, vertex_attrs_df, on='points')
|
|
139
|
+
# Create a dictionary to hold the numpy arrays for each component lith
|
|
140
|
+
return merged_df
|