subsurface-terra 2025.1.0rc15__py3-none-any.whl → 2025.1.0rc16__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.
Files changed (81) hide show
  1. subsurface/__init__.py +31 -31
  2. subsurface/_version.py +34 -21
  3. subsurface/api/__init__.py +13 -13
  4. subsurface/api/interfaces/__init__.py +3 -3
  5. subsurface/api/interfaces/stream.py +136 -136
  6. subsurface/api/reader/read_wells.py +78 -78
  7. subsurface/core/geological_formats/boreholes/_combine_trajectories.py +117 -117
  8. subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py +236 -234
  9. subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py +163 -163
  10. subsurface/core/geological_formats/boreholes/boreholes.py +140 -140
  11. subsurface/core/geological_formats/boreholes/collars.py +26 -26
  12. subsurface/core/geological_formats/boreholes/survey.py +86 -86
  13. subsurface/core/geological_formats/fault.py +47 -47
  14. subsurface/core/reader_helpers/reader_unstruct.py +11 -11
  15. subsurface/core/reader_helpers/readers_data.py +130 -130
  16. subsurface/core/reader_helpers/readers_wells.py +13 -13
  17. subsurface/core/structs/__init__.py +3 -3
  18. subsurface/core/structs/base_structures/__init__.py +2 -2
  19. subsurface/core/structs/base_structures/_liquid_earth_mesh.py +121 -121
  20. subsurface/core/structs/base_structures/_unstructured_data_constructor.py +70 -70
  21. subsurface/core/structs/base_structures/base_structures_enum.py +6 -6
  22. subsurface/core/structs/base_structures/structured_data.py +282 -282
  23. subsurface/core/structs/base_structures/unstructured_data.py +319 -319
  24. subsurface/core/structs/structured_elements/octree_mesh.py +10 -10
  25. subsurface/core/structs/structured_elements/structured_grid.py +59 -59
  26. subsurface/core/structs/structured_elements/structured_mesh.py +9 -9
  27. subsurface/core/structs/unstructured_elements/__init__.py +3 -3
  28. subsurface/core/structs/unstructured_elements/line_set.py +72 -72
  29. subsurface/core/structs/unstructured_elements/point_set.py +43 -43
  30. subsurface/core/structs/unstructured_elements/tetrahedron_mesh.py +35 -35
  31. subsurface/core/structs/unstructured_elements/triangular_surface.py +62 -62
  32. subsurface/core/utils/utils_core.py +38 -38
  33. subsurface/modules/reader/__init__.py +13 -13
  34. subsurface/modules/reader/faults/faults.py +80 -80
  35. subsurface/modules/reader/from_binary.py +46 -46
  36. subsurface/modules/reader/mesh/_GOCAD_mesh.py +82 -82
  37. subsurface/modules/reader/mesh/_trimesh_reader.py +447 -447
  38. subsurface/modules/reader/mesh/csv_mesh_reader.py +53 -53
  39. subsurface/modules/reader/mesh/dxf_reader.py +177 -177
  40. subsurface/modules/reader/mesh/glb_reader.py +30 -30
  41. subsurface/modules/reader/mesh/mx_reader.py +232 -232
  42. subsurface/modules/reader/mesh/obj_reader.py +53 -53
  43. subsurface/modules/reader/mesh/omf_mesh_reader.py +43 -43
  44. subsurface/modules/reader/mesh/surface_reader.py +56 -56
  45. subsurface/modules/reader/mesh/surfaces_api.py +41 -41
  46. subsurface/modules/reader/profiles/__init__.py +3 -3
  47. subsurface/modules/reader/profiles/profiles_core.py +197 -197
  48. subsurface/modules/reader/read_netcdf.py +38 -38
  49. subsurface/modules/reader/topography/__init__.py +7 -7
  50. subsurface/modules/reader/topography/topo_core.py +100 -100
  51. subsurface/modules/reader/volume/read_grav3d.py +478 -428
  52. subsurface/modules/reader/volume/read_volume.py +327 -230
  53. subsurface/modules/reader/volume/segy_reader.py +105 -105
  54. subsurface/modules/reader/volume/seismic.py +173 -173
  55. subsurface/modules/reader/volume/volume_utils.py +43 -43
  56. subsurface/modules/reader/wells/DEP/__init__.py +43 -43
  57. subsurface/modules/reader/wells/DEP/_well_files_reader.py +167 -167
  58. subsurface/modules/reader/wells/DEP/_wells_api.py +61 -61
  59. subsurface/modules/reader/wells/DEP/_welly_reader.py +180 -180
  60. subsurface/modules/reader/wells/DEP/pandas_to_welly.py +212 -212
  61. subsurface/modules/reader/wells/_read_to_df.py +57 -57
  62. subsurface/modules/reader/wells/read_borehole_interface.py +148 -148
  63. subsurface/modules/reader/wells/wells_utils.py +68 -68
  64. subsurface/modules/tools/mocking_aux.py +104 -104
  65. subsurface/modules/visualization/__init__.py +2 -2
  66. subsurface/modules/visualization/to_pyvista.py +320 -320
  67. subsurface/modules/writer/to_binary.py +12 -12
  68. subsurface/modules/writer/to_rex/common.py +78 -78
  69. subsurface/modules/writer/to_rex/data_struct.py +74 -74
  70. subsurface/modules/writer/to_rex/gempy_to_rexfile.py +791 -791
  71. subsurface/modules/writer/to_rex/material_encoder.py +44 -44
  72. subsurface/modules/writer/to_rex/mesh_encoder.py +152 -152
  73. subsurface/modules/writer/to_rex/to_rex.py +115 -115
  74. subsurface/modules/writer/to_rex/utils.py +15 -15
  75. subsurface/optional_requirements.py +116 -116
  76. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/METADATA +194 -194
  77. subsurface_terra-2025.1.0rc16.dist-info/RECORD +98 -0
  78. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/WHEEL +1 -1
  79. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/licenses/LICENSE +203 -203
  80. subsurface_terra-2025.1.0rc15.dist-info/RECORD +0 -98
  81. {subsurface_terra-2025.1.0rc15.dist-info → subsurface_terra-2025.1.0rc16.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