xtgeo 4.8.0__cp313-cp313-macosx_11_0_arm64.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.

Potentially problematic release.


This version of xtgeo might be problematic. Click here for more details.

Files changed (117) hide show
  1. cxtgeo.py +582 -0
  2. cxtgeoPYTHON_wrap.c +20938 -0
  3. xtgeo/__init__.py +246 -0
  4. xtgeo/_cxtgeo.cpython-313-darwin.so +0 -0
  5. xtgeo/_internal.cpython-313-darwin.so +0 -0
  6. xtgeo/common/__init__.py +19 -0
  7. xtgeo/common/_angles.py +29 -0
  8. xtgeo/common/_xyz_enum.py +50 -0
  9. xtgeo/common/calc.py +396 -0
  10. xtgeo/common/constants.py +30 -0
  11. xtgeo/common/exceptions.py +42 -0
  12. xtgeo/common/log.py +93 -0
  13. xtgeo/common/sys.py +166 -0
  14. xtgeo/common/types.py +18 -0
  15. xtgeo/common/version.py +21 -0
  16. xtgeo/common/xtgeo_dialog.py +604 -0
  17. xtgeo/cube/__init__.py +9 -0
  18. xtgeo/cube/_cube_export.py +214 -0
  19. xtgeo/cube/_cube_import.py +532 -0
  20. xtgeo/cube/_cube_roxapi.py +180 -0
  21. xtgeo/cube/_cube_utils.py +287 -0
  22. xtgeo/cube/_cube_window_attributes.py +340 -0
  23. xtgeo/cube/cube1.py +1023 -0
  24. xtgeo/grid3d/__init__.py +15 -0
  25. xtgeo/grid3d/_ecl_grid.py +774 -0
  26. xtgeo/grid3d/_ecl_inte_head.py +148 -0
  27. xtgeo/grid3d/_ecl_logi_head.py +71 -0
  28. xtgeo/grid3d/_ecl_output_file.py +81 -0
  29. xtgeo/grid3d/_egrid.py +1004 -0
  30. xtgeo/grid3d/_find_gridprop_in_eclrun.py +625 -0
  31. xtgeo/grid3d/_grdecl_format.py +266 -0
  32. xtgeo/grid3d/_grdecl_grid.py +388 -0
  33. xtgeo/grid3d/_grid3d.py +29 -0
  34. xtgeo/grid3d/_grid3d_fence.py +181 -0
  35. xtgeo/grid3d/_grid3d_utils.py +228 -0
  36. xtgeo/grid3d/_grid_boundary.py +76 -0
  37. xtgeo/grid3d/_grid_etc1.py +1566 -0
  38. xtgeo/grid3d/_grid_export.py +221 -0
  39. xtgeo/grid3d/_grid_hybrid.py +66 -0
  40. xtgeo/grid3d/_grid_import.py +79 -0
  41. xtgeo/grid3d/_grid_import_ecl.py +101 -0
  42. xtgeo/grid3d/_grid_import_roff.py +135 -0
  43. xtgeo/grid3d/_grid_import_xtgcpgeom.py +375 -0
  44. xtgeo/grid3d/_grid_refine.py +125 -0
  45. xtgeo/grid3d/_grid_roxapi.py +292 -0
  46. xtgeo/grid3d/_grid_wellzone.py +165 -0
  47. xtgeo/grid3d/_gridprop_export.py +178 -0
  48. xtgeo/grid3d/_gridprop_import_eclrun.py +164 -0
  49. xtgeo/grid3d/_gridprop_import_grdecl.py +130 -0
  50. xtgeo/grid3d/_gridprop_import_roff.py +52 -0
  51. xtgeo/grid3d/_gridprop_import_xtgcpprop.py +168 -0
  52. xtgeo/grid3d/_gridprop_lowlevel.py +171 -0
  53. xtgeo/grid3d/_gridprop_op1.py +174 -0
  54. xtgeo/grid3d/_gridprop_roxapi.py +239 -0
  55. xtgeo/grid3d/_gridprop_value_init.py +140 -0
  56. xtgeo/grid3d/_gridprops_import_eclrun.py +344 -0
  57. xtgeo/grid3d/_gridprops_import_roff.py +83 -0
  58. xtgeo/grid3d/_roff_grid.py +469 -0
  59. xtgeo/grid3d/_roff_parameter.py +303 -0
  60. xtgeo/grid3d/grid.py +2537 -0
  61. xtgeo/grid3d/grid_properties.py +699 -0
  62. xtgeo/grid3d/grid_property.py +1341 -0
  63. xtgeo/grid3d/types.py +15 -0
  64. xtgeo/io/__init__.py +1 -0
  65. xtgeo/io/_file.py +592 -0
  66. xtgeo/metadata/__init__.py +17 -0
  67. xtgeo/metadata/metadata.py +431 -0
  68. xtgeo/roxutils/__init__.py +7 -0
  69. xtgeo/roxutils/_roxar_loader.py +54 -0
  70. xtgeo/roxutils/_roxutils_etc.py +122 -0
  71. xtgeo/roxutils/roxutils.py +207 -0
  72. xtgeo/surface/__init__.py +18 -0
  73. xtgeo/surface/_regsurf_boundary.py +26 -0
  74. xtgeo/surface/_regsurf_cube.py +210 -0
  75. xtgeo/surface/_regsurf_cube_window.py +391 -0
  76. xtgeo/surface/_regsurf_cube_window_v2.py +297 -0
  77. xtgeo/surface/_regsurf_cube_window_v3.py +360 -0
  78. xtgeo/surface/_regsurf_export.py +388 -0
  79. xtgeo/surface/_regsurf_grid3d.py +271 -0
  80. xtgeo/surface/_regsurf_gridding.py +347 -0
  81. xtgeo/surface/_regsurf_ijxyz_parser.py +278 -0
  82. xtgeo/surface/_regsurf_import.py +347 -0
  83. xtgeo/surface/_regsurf_lowlevel.py +122 -0
  84. xtgeo/surface/_regsurf_oper.py +631 -0
  85. xtgeo/surface/_regsurf_roxapi.py +241 -0
  86. xtgeo/surface/_regsurf_utils.py +81 -0
  87. xtgeo/surface/_surfs_import.py +43 -0
  88. xtgeo/surface/_zmap_parser.py +138 -0
  89. xtgeo/surface/regular_surface.py +2967 -0
  90. xtgeo/surface/surfaces.py +276 -0
  91. xtgeo/well/__init__.py +24 -0
  92. xtgeo/well/_blockedwell_roxapi.py +221 -0
  93. xtgeo/well/_blockedwells_roxapi.py +68 -0
  94. xtgeo/well/_well_aux.py +30 -0
  95. xtgeo/well/_well_io.py +327 -0
  96. xtgeo/well/_well_oper.py +574 -0
  97. xtgeo/well/_well_roxapi.py +304 -0
  98. xtgeo/well/_wellmarkers.py +486 -0
  99. xtgeo/well/_wells_utils.py +158 -0
  100. xtgeo/well/blocked_well.py +216 -0
  101. xtgeo/well/blocked_wells.py +122 -0
  102. xtgeo/well/well1.py +1514 -0
  103. xtgeo/well/wells.py +211 -0
  104. xtgeo/xyz/__init__.py +6 -0
  105. xtgeo/xyz/_polygons_oper.py +272 -0
  106. xtgeo/xyz/_xyz.py +741 -0
  107. xtgeo/xyz/_xyz_data.py +646 -0
  108. xtgeo/xyz/_xyz_io.py +490 -0
  109. xtgeo/xyz/_xyz_lowlevel.py +42 -0
  110. xtgeo/xyz/_xyz_oper.py +613 -0
  111. xtgeo/xyz/_xyz_roxapi.py +766 -0
  112. xtgeo/xyz/points.py +681 -0
  113. xtgeo/xyz/polygons.py +811 -0
  114. xtgeo-4.8.0.dist-info/METADATA +145 -0
  115. xtgeo-4.8.0.dist-info/RECORD +117 -0
  116. xtgeo-4.8.0.dist-info/WHEEL +6 -0
  117. xtgeo-4.8.0.dist-info/licenses/LICENSE.md +165 -0
@@ -0,0 +1,287 @@
1
+ """Cube utilities (basic low level)"""
2
+
3
+ import warnings
4
+
5
+ import numpy as np
6
+
7
+ from xtgeo import _cxtgeo
8
+ from xtgeo._cxtgeo import XTGeoCLibError
9
+ from xtgeo.common.calc import _swap_axes
10
+ from xtgeo.common.constants import UNDEF_LIMIT
11
+ from xtgeo.common.log import null_logger
12
+ from xtgeo.xyz.polygons import Polygons
13
+
14
+ logger = null_logger(__name__)
15
+
16
+
17
+ def swapaxes(self):
18
+ """Pure numpy/python version"""
19
+ self._rotation, self._yflip, swapped_values = _swap_axes(
20
+ self._rotation,
21
+ self._yflip,
22
+ values=self._values,
23
+ traceidcodes=self._traceidcodes,
24
+ )
25
+ self._ncol, self._nrow = self._nrow, self._ncol
26
+ self._xinc, self._yinc = self._yinc, self._xinc
27
+ self.values = swapped_values["values"]
28
+ self._traceidcodes = swapped_values["traceidcodes"]
29
+
30
+
31
+ def thinning(self, icol, jrow, klay):
32
+ inputs = [icol, jrow, klay]
33
+ ranges = [self.nrow, self.ncol, self.nlay]
34
+
35
+ for inum, ixc in enumerate(inputs):
36
+ if not isinstance(ixc, int):
37
+ raise ValueError(f"Some input is not integer: {inputs}")
38
+ if ixc > ranges[inum] / 2:
39
+ raise ValueError(
40
+ f"Input numbers <{inputs}> are too large compared to existing "
41
+ f"ranges <{ranges}>"
42
+ )
43
+
44
+ # just simple numpy operations, and changing some cube props
45
+
46
+ val = self.values.copy()
47
+
48
+ val = val[::icol, ::jrow, ::klay]
49
+ self._ncol = val.shape[0]
50
+ self._nrow = val.shape[1]
51
+ self._nlay = val.shape[2]
52
+ self._xinc *= icol
53
+ self._yinc *= jrow
54
+ self._zinc *= klay
55
+ self._ilines = self._ilines[::icol]
56
+ self._xlines = self._xlines[::jrow]
57
+ self._traceidcodes = self._traceidcodes[::icol, ::jrow]
58
+
59
+ self.values = val
60
+
61
+
62
+ def cropping(self, icols, jrows, klays):
63
+ """Cropping, where inputs are tuples"""
64
+
65
+ icol1, icol2 = icols
66
+ jrow1, jrow2 = jrows
67
+ klay1, klay2 = klays
68
+
69
+ val = self.values.copy()
70
+ ncol = self.ncol
71
+ nrow = self.nrow
72
+ nlay = self.nlay
73
+
74
+ val = val[
75
+ 0 + icol1 : ncol - icol2, 0 + jrow1 : nrow - jrow2, 0 + klay1 : nlay - klay2
76
+ ]
77
+
78
+ self._ncol = val.shape[0]
79
+ self._nrow = val.shape[1]
80
+ self._nlay = val.shape[2]
81
+
82
+ self._ilines = self._ilines[0 + icol1 : ncol - icol2]
83
+ self._xlines = self._xlines[0 + jrow1 : nrow - jrow2]
84
+ self.traceidcodes = self.traceidcodes[
85
+ 0 + icol1 : ncol - icol2, 0 + jrow1 : nrow - jrow2
86
+ ]
87
+
88
+ # 1 + .., since the following routine as 1 as base for i j
89
+ ier, xpp, ypp = _cxtgeo.cube_xy_from_ij(
90
+ 1 + icol1,
91
+ 1 + jrow1,
92
+ self.xori,
93
+ self.xinc,
94
+ self.yori,
95
+ self.yinc,
96
+ ncol,
97
+ nrow,
98
+ self.yflip,
99
+ self.rotation,
100
+ 0,
101
+ )
102
+
103
+ if ier != 0:
104
+ raise RuntimeError(f"Unexpected error, code is {ier}")
105
+
106
+ # get new X Y origins
107
+ self._xori = xpp
108
+ self._yori = ypp
109
+ self._zori = self.zori + klay1 * self.zinc
110
+
111
+ self.values = val
112
+
113
+
114
+ def resample(self, other, sampling="nearest", outside_value=None):
115
+ """Resample another cube to the current self"""
116
+ # TODO: traceidcodes
117
+
118
+ values1a = self.values.reshape(-1)
119
+ values2a = other.values.reshape(-1)
120
+
121
+ logger.info("Resampling, using %s...", sampling)
122
+
123
+ ier = _cxtgeo.cube_resample_cube(
124
+ self.ncol,
125
+ self.nrow,
126
+ self.nlay,
127
+ self.xori,
128
+ self.xinc,
129
+ self.yori,
130
+ self.yinc,
131
+ self.zori,
132
+ self.zinc,
133
+ self.rotation,
134
+ self.yflip,
135
+ values1a,
136
+ other.ncol,
137
+ other.nrow,
138
+ other.nlay,
139
+ other.xori,
140
+ other.xinc,
141
+ other.yori,
142
+ other.yinc,
143
+ other.zori,
144
+ other.zinc,
145
+ other.rotation,
146
+ other.yflip,
147
+ values2a,
148
+ 1 if sampling == "trilinear" else 0,
149
+ 0 if outside_value is None else 1,
150
+ 0 if outside_value is None else outside_value,
151
+ )
152
+ if ier == -4:
153
+ warnings.warn("Less than 10% of origonal cube sampled", RuntimeWarning)
154
+ elif ier != 0:
155
+ raise XTGeoCLibError("cube_resample_cube failed to complete")
156
+
157
+
158
+ def get_xy_value_from_ij(self, iloc, jloc, ixline=False, zerobased=False):
159
+ """Find X Y value from I J index, or corresponding inline/xline"""
160
+ # assumes that inline follows I and xlines follows J
161
+
162
+ iuse = iloc
163
+ juse = jloc
164
+
165
+ if zerobased:
166
+ iuse = iuse + 1
167
+ juse = juse + 1
168
+
169
+ if ixline:
170
+ ilst = self.ilines.tolist()
171
+ jlst = self.xlines.tolist()
172
+ iuse = ilst.index(iloc) + 1
173
+ juse = jlst.index(jloc) + 1
174
+
175
+ if 1 <= iuse <= self.ncol and 1 <= juse <= self.nrow:
176
+ ier, xval, yval = _cxtgeo.cube_xy_from_ij(
177
+ iuse,
178
+ juse,
179
+ self.xori,
180
+ self.xinc,
181
+ self.yori,
182
+ self.yinc,
183
+ self.ncol,
184
+ self.nrow,
185
+ self._yflip,
186
+ self.rotation,
187
+ 0,
188
+ )
189
+ if ier != 0:
190
+ raise XTGeoCLibError(f"cube_xy_from_ij failed with error code: {ier}")
191
+
192
+ else:
193
+ raise ValueError("Index i and/or j out of bounds")
194
+
195
+ return xval, yval
196
+
197
+
198
+ def get_randomline(
199
+ self,
200
+ fencespec,
201
+ zmin=None,
202
+ zmax=None,
203
+ zincrement=None,
204
+ hincrement=None,
205
+ atleast=5,
206
+ nextend=2,
207
+ sampling="nearest",
208
+ ):
209
+ """Get a random line from a fence spesification"""
210
+
211
+ if isinstance(fencespec, Polygons):
212
+ logger.info("Estimate hincrement from Polygons instance...")
213
+ fencespec = _get_randomline_fence(self, fencespec, hincrement, atleast, nextend)
214
+ logger.info("Estimate hincrement from Polygons instance... DONE")
215
+
216
+ if not len(fencespec.shape) == 2:
217
+ raise ValueError("Fence is not a 2D numpy")
218
+
219
+ xcoords = fencespec[:, 0]
220
+ ycoords = fencespec[:, 1]
221
+ hcoords = fencespec[:, 3]
222
+
223
+ for ino in range(hcoords.shape[0] - 1):
224
+ dhv = hcoords[ino + 1] - hcoords[ino]
225
+ logger.info("Delta H along well path: %s", dhv)
226
+
227
+ zcubemax = self._zori + (self._nlay - 1) * self._zinc
228
+ if zmin is None or zmin < self._zori:
229
+ zmin = self._zori
230
+
231
+ if zmax is None or zmax > zcubemax:
232
+ zmax = zcubemax
233
+
234
+ if zincrement is None:
235
+ zincrement = self._zinc / 2.0
236
+
237
+ nzsam = int((zmax - zmin) / zincrement) + 1
238
+
239
+ nsamples = xcoords.shape[0] * nzsam
240
+
241
+ option = 0
242
+ if sampling == "trilinear":
243
+ option = 1
244
+
245
+ _ier, values = _cxtgeo.cube_get_randomline(
246
+ xcoords,
247
+ ycoords,
248
+ zmin,
249
+ zmax,
250
+ nzsam,
251
+ self._xori,
252
+ self._xinc,
253
+ self._yori,
254
+ self._yinc,
255
+ self._zori,
256
+ self._zinc,
257
+ self._rotation,
258
+ self._yflip,
259
+ self._ncol,
260
+ self._nrow,
261
+ self._nlay,
262
+ self._values.reshape(-1),
263
+ nsamples,
264
+ option,
265
+ )
266
+
267
+ values[values > UNDEF_LIMIT] = np.nan
268
+ arr = values.reshape((xcoords.shape[0], nzsam)).T
269
+
270
+ return (hcoords[0], hcoords[-1], zmin, zmax, arr)
271
+
272
+
273
+ def _get_randomline_fence(self, fencespec, hincrement, atleast, nextend):
274
+ """Compute a resampled fence from a Polygons instance"""
275
+
276
+ if hincrement is None:
277
+ avgdxdy = 0.5 * (self.xinc + self.yinc)
278
+ distance = 0.5 * avgdxdy
279
+ else:
280
+ distance = hincrement
281
+
282
+ logger.info("Getting fence from a Polygons instance...")
283
+ fspec = fencespec.get_fence(
284
+ distance=distance, atleast=atleast, nextend=nextend, asnumpy=True
285
+ )
286
+ logger.info("Getting fence from a Polygons instance... DONE")
287
+ return fspec
@@ -0,0 +1,340 @@
1
+ """Attributes for a Cube to maps (surfaces), slice an interval, in pure numpy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import warnings
6
+ from dataclasses import dataclass, field
7
+ from typing import TYPE_CHECKING, Final
8
+
9
+ import numpy as np
10
+ from scipy.interpolate import make_interp_spline
11
+
12
+ import xtgeo._internal as _internal # type: ignore
13
+ from xtgeo.common.log import null_logger
14
+
15
+ if TYPE_CHECKING:
16
+ from xtgeo.surface.regular_surface import RegularSurface
17
+
18
+ from . import Cube
19
+
20
+ logger = null_logger(__name__)
21
+
22
+
23
+ STAT_ATTRS: Final = [
24
+ "min",
25
+ "max",
26
+ "mean",
27
+ "var",
28
+ "rms",
29
+ "maxpos",
30
+ "maxneg",
31
+ "maxabs",
32
+ "meanpos",
33
+ "meanneg",
34
+ "meanabs",
35
+ ]
36
+ SUM_ATTRS: Final = [
37
+ "sumpos",
38
+ "sumneg",
39
+ "sumabs",
40
+ ]
41
+
42
+
43
+ @dataclass
44
+ class CubeAttrs:
45
+ """Internal class for computing attributes in window between two surfaces."""
46
+
47
+ cube: Cube
48
+ upper_surface: RegularSurface | float | int
49
+ lower_surface: RegularSurface | float | int
50
+ ndiv: int = 10
51
+ interpolation: str = "cubic" # cf. scipy's make_interp_spline() when k=3
52
+ minimum_thickness: float = 0.0
53
+
54
+ # internal attributes
55
+ _template_surface: RegularSurface | None = None
56
+ _depth_array: np.ndarray | None = None
57
+ _outside_depth: float | None = None # detected and updated from the depth cube
58
+ _reduced_cube: Cube = None
59
+ _reduced_depth_array: np.ndarray | None = None
60
+ _refined_cube: Cube | None = None
61
+ _refined_depth_array: np.ndarray | None = None
62
+
63
+ _upper: RegularSurface | None = None # upper surf, resampled to cube map resolution
64
+ _lower: RegularSurface | None = None # lower surf, resampled to cube map resolution
65
+ _min_thickness_mask: RegularSurface | None = None # mask for min. thickness trunc.
66
+
67
+ _result_attr_maps: dict = field(default_factory=dict) # holds the resulting maps
68
+
69
+ def __post_init__(self) -> None:
70
+ self._process_upper_lower_surface()
71
+ self._create_depth_array()
72
+ self._create_reduced_cube()
73
+ self._refine_interpolate()
74
+ self._depth_mask()
75
+ self._compute_statistical_attribute_surfaces()
76
+
77
+ def result(self) -> dict[RegularSurface]:
78
+ # return the resulting attribute maps
79
+ return self._result_attr_maps
80
+
81
+ def _process_upper_lower_surface(self) -> None:
82
+ """Extract upper and lower surface, sampled to cube resolution."""
83
+
84
+ from xtgeo import surface_from_cube # avoid circular import by having this here
85
+
86
+ upper = (
87
+ surface_from_cube(self.cube, self.upper_surface)
88
+ if isinstance(self.upper_surface, (float, int))
89
+ else self.upper_surface
90
+ )
91
+ lower = (
92
+ surface_from_cube(self.cube, self.lower_surface)
93
+ if isinstance(self.lower_surface, (float, int))
94
+ else self.lower_surface
95
+ )
96
+
97
+ self._template_surface = (
98
+ upper
99
+ if isinstance(self.upper_surface, (float, int))
100
+ else self.upper_surface
101
+ )
102
+
103
+ # determine which of "this" and "other" is actually upper and lower
104
+ if (lower - upper).values.mean() < 0:
105
+ raise ValueError(
106
+ "The upper surface is below the lower surface. "
107
+ "Please provide the surfaces in the correct order."
108
+ )
109
+
110
+ # although not an attribute, we store the upper and lower surfaces
111
+ self._result_attr_maps["upper"] = upper
112
+ self._result_attr_maps["lower"] = lower
113
+
114
+ # get the surfaces on cube resolution
115
+ self._upper = surface_from_cube(self.cube, self.cube.zori)
116
+ self._lower = surface_from_cube(self.cube, self.cube.zori)
117
+ self._upper.resample(upper)
118
+ self._lower.resample(lower)
119
+
120
+ self._upper.fill()
121
+ self._lower.fill()
122
+
123
+ self._min_thickness_mask = self._lower - self._upper
124
+
125
+ self._min_thickness_mask.values = np.where(
126
+ self._min_thickness_mask.values <= self.minimum_thickness, 0, 1
127
+ )
128
+ if np.all(self._min_thickness_mask.values == 0):
129
+ raise ValueError(
130
+ "The minimum thickness is too large, no valid data in the interval. "
131
+ "Perhaps surfaces are overlapping?"
132
+ )
133
+
134
+ def _create_depth_array(self) -> None:
135
+ """Create a 1D array where values are cube depths; to be used as filter.
136
+
137
+ Belowe and above the input surfaces (plus a buffer), the values are set to
138
+ a constant value self._outside_depth.
139
+
140
+ Will also issue warnings or errors if the surfaces are outside the cube,
141
+ depending on severity.
142
+ """
143
+
144
+ self._depth_array = np.array(
145
+ [
146
+ self.cube.zori + n * self.cube.zinc
147
+ for n in range(self.cube.values.shape[2])
148
+ ]
149
+ ).astype(np.float32)
150
+
151
+ # check that surfaces are within the cube
152
+ if self._upper.values.min() > self._depth_array.max():
153
+ raise ValueError("Upper surface is fully below the cube")
154
+ if self._lower.values.max() < self._depth_array.min():
155
+ raise ValueError("Lower surface is fully above the cube")
156
+ if self._upper.values.max() < self._depth_array.min():
157
+ warnings.warn("Upper surface is fully above the cube", UserWarning)
158
+ if self._lower.values.min() > self._depth_array.max():
159
+ warnings.warn("Lower surface is fully below the cube", UserWarning)
160
+
161
+ self._outside_depth = self._depth_array.max() + 1
162
+
163
+ add_extra_depth = 2 * self.cube.zinc # add buffer on upper/lower edges
164
+
165
+ self._depth_array = np.where(
166
+ self._depth_array < self._upper.values.min() - add_extra_depth,
167
+ self._outside_depth,
168
+ self._depth_array,
169
+ )
170
+
171
+ self._depth_array = np.where(
172
+ self._depth_array > self._lower.values.max() + add_extra_depth,
173
+ self._outside_depth,
174
+ self._depth_array,
175
+ )
176
+
177
+ def _create_reduced_cube(self) -> None:
178
+ """Create a smaller cube based on the depth cube filter.
179
+
180
+ The purpose is to limit the computation to the relevant volume, to save
181
+ CPU time. I.e. cube values above the upper surface and below the lower are
182
+ now excluded.
183
+ """
184
+ from xtgeo import Cube # avoid circular import by having this here
185
+
186
+ cubev = self.cube.values.copy() # copy, so we don't change the input instance
187
+ cubev[self.cube.traceidcodes == 2] = np.nan # set dead traces to nan
188
+
189
+ # Create a boolean mask based on the threshold
190
+ mask = self._depth_array < self._outside_depth
191
+
192
+ # Find the bounding box of the true values
193
+ non_zero_indices = np.nonzero(mask)[0]
194
+
195
+ if len(non_zero_indices) == 0:
196
+ raise RuntimeError( # e.g. if cube and surfaces are at different locations
197
+ "No valid data found in the depth cube. Perhaps the surfaces are "
198
+ "outside the cube?"
199
+ )
200
+
201
+ min_indices = np.min(non_zero_indices)
202
+ max_indices = np.max(non_zero_indices) + 1 # Add 1 to include the upper bound
203
+
204
+ # Extract the reduced cube using slicing
205
+ reduced = cubev[:, :, min_indices:max_indices]
206
+
207
+ zori = float(self._depth_array.min())
208
+
209
+ self._reduced_cube = Cube(
210
+ ncol=reduced.shape[0],
211
+ nrow=reduced.shape[1],
212
+ nlay=reduced.shape[2],
213
+ xinc=self.cube.xinc,
214
+ yinc=self.cube.yinc,
215
+ zinc=self.cube.zinc,
216
+ xori=self.cube.xori,
217
+ yori=self.cube.yori,
218
+ zori=zori,
219
+ rotation=self.cube.rotation,
220
+ yflip=self.cube.yflip,
221
+ values=reduced.astype(np.float32),
222
+ )
223
+
224
+ self._reduced_depth_array = self._depth_array[min_indices:max_indices]
225
+
226
+ logger.debug("Reduced cubes created %s", self._reduced_cube.values.shape)
227
+
228
+ def _refine_interpolate(self) -> None:
229
+ """Apply reduced cubes and interpolate to a finer grid vertically.
230
+
231
+ This is done to get a more accurate representation of the cube values.
232
+ """
233
+ from xtgeo import Cube
234
+
235
+ logger.debug("Refine cubes and interpolate...")
236
+ arr = self._reduced_cube.values
237
+ ndiv = self.ndiv
238
+
239
+ # Create linear interpolation function along the last axis
240
+ fdepth = make_interp_spline(
241
+ np.arange(arr.shape[2]),
242
+ self._reduced_depth_array,
243
+ axis=0,
244
+ k=1,
245
+ )
246
+
247
+ # Create interpolation function along the last axis
248
+ if self.interpolation not in ["cubic", "linear"]:
249
+ raise ValueError("Interpolation must be either 'cubic' or 'linear'")
250
+
251
+ fcube = make_interp_spline(
252
+ np.arange(arr.shape[2]),
253
+ arr,
254
+ axis=2,
255
+ k=3 if self.interpolation == "cubic" else 1,
256
+ )
257
+ # Define new sampling points along the last axis
258
+ new_z = np.linspace(0, arr.shape[2] - 1, arr.shape[2] * ndiv)
259
+
260
+ # Resample the cube array
261
+ resampled_arr = fcube(new_z)
262
+
263
+ # Resample the depth array (always linear)
264
+ self._refined_depth_array = new_depth = fdepth(new_z)
265
+ new_zinc = (new_depth.max() - new_depth.min()) / (new_depth.shape[0] - 1)
266
+
267
+ self._refined_cube = Cube(
268
+ ncol=resampled_arr.shape[0],
269
+ nrow=resampled_arr.shape[1],
270
+ nlay=resampled_arr.shape[2],
271
+ xinc=self.cube.xinc,
272
+ yinc=self.cube.yinc,
273
+ zinc=new_zinc,
274
+ xori=self.cube.xori,
275
+ yori=self.cube.yori,
276
+ zori=self._refined_depth_array.min(),
277
+ rotation=self._reduced_cube.rotation,
278
+ yflip=self._reduced_cube.yflip,
279
+ values=resampled_arr.astype(np.float32),
280
+ )
281
+
282
+ def _depth_mask(self) -> None:
283
+ """Set nan values outside the interval defined by the upper + lower surface.
284
+
285
+ In addition, set nan values where the thickness is less than the minimum.
286
+
287
+ """
288
+
289
+ darry = np.expand_dims(self._refined_depth_array, axis=(0, 1))
290
+ upper_exp = np.expand_dims(self._upper.values, 2)
291
+ lower_exp = np.expand_dims(self._lower.values, 2)
292
+ mask_2d_exp = np.expand_dims(self._min_thickness_mask.values, 2)
293
+
294
+ self._refined_cube.values = np.where(
295
+ (darry < upper_exp) | (darry > lower_exp) | (mask_2d_exp == 0),
296
+ np.nan,
297
+ self._refined_cube.values,
298
+ ).astype(np.float32)
299
+
300
+ # similar for reduced cubes with original resolution
301
+ darry = np.expand_dims(self._reduced_depth_array, axis=(0, 1))
302
+
303
+ self._reduced_cube.values = np.where(
304
+ (darry < upper_exp) | (darry > lower_exp) | (mask_2d_exp == 0),
305
+ np.nan,
306
+ self._reduced_cube.values,
307
+ ).astype(np.float32)
308
+
309
+ def _add_to_attribute_map(self, attr_name: str, values: np.ndarray) -> None:
310
+ """Compute the attribute map and add to result dictionary."""
311
+ attr_map = self._upper.copy()
312
+ attr_map.values = np.ma.masked_invalid(values)
313
+
314
+ # now resample to the original input map
315
+ attr_map_resampled = self._template_surface.copy()
316
+ attr_map_resampled.resample(attr_map)
317
+
318
+ attr_map_resampled.values = np.ma.masked_where(
319
+ self.upper_surface.values.mask, attr_map_resampled.values
320
+ )
321
+
322
+ self._result_attr_maps[attr_name] = attr_map_resampled
323
+
324
+ def _compute_statistical_attribute_surfaces(self) -> None:
325
+ """Compute stats very fast by using internal C++ bindings."""
326
+
327
+ # compute statistics for vertically refined cube
328
+ cubecpp = _internal.cube.Cube(self._refined_cube)
329
+ all_attrs = cubecpp.cube_stats_along_z()
330
+
331
+ for attr in STAT_ATTRS:
332
+ self._add_to_attribute_map(attr, all_attrs[attr])
333
+
334
+ # compute statistics for reduced cube (for sum attributes)
335
+ cubecpp = _internal.cube.Cube(self._reduced_cube)
336
+ all_attrs = cubecpp.cube_stats_along_z()
337
+
338
+ # add sum attributes which are the last 3 in the list
339
+ for attr in SUM_ATTRS:
340
+ self._add_to_attribute_map(attr, all_attrs[attr])