xtgeo 4.14.1__cp313-cp313-win_amd64.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.
- cxtgeo.py +558 -0
- cxtgeoPYTHON_wrap.c +19537 -0
- xtgeo/__init__.py +248 -0
- xtgeo/_cxtgeo.cp313-win_amd64.pyd +0 -0
- xtgeo/_internal.cp313-win_amd64.pyd +0 -0
- xtgeo/common/__init__.py +19 -0
- xtgeo/common/_angles.py +29 -0
- xtgeo/common/_xyz_enum.py +50 -0
- xtgeo/common/calc.py +396 -0
- xtgeo/common/constants.py +30 -0
- xtgeo/common/exceptions.py +42 -0
- xtgeo/common/log.py +93 -0
- xtgeo/common/sys.py +166 -0
- xtgeo/common/types.py +18 -0
- xtgeo/common/version.py +34 -0
- xtgeo/common/xtgeo_dialog.py +604 -0
- xtgeo/cube/__init__.py +9 -0
- xtgeo/cube/_cube_export.py +214 -0
- xtgeo/cube/_cube_import.py +532 -0
- xtgeo/cube/_cube_roxapi.py +180 -0
- xtgeo/cube/_cube_utils.py +287 -0
- xtgeo/cube/_cube_window_attributes.py +273 -0
- xtgeo/cube/cube1.py +1023 -0
- xtgeo/grid3d/__init__.py +15 -0
- xtgeo/grid3d/_ecl_grid.py +778 -0
- xtgeo/grid3d/_ecl_inte_head.py +152 -0
- xtgeo/grid3d/_ecl_logi_head.py +71 -0
- xtgeo/grid3d/_ecl_output_file.py +81 -0
- xtgeo/grid3d/_egrid.py +1004 -0
- xtgeo/grid3d/_find_gridprop_in_eclrun.py +625 -0
- xtgeo/grid3d/_grdecl_format.py +309 -0
- xtgeo/grid3d/_grdecl_grid.py +400 -0
- xtgeo/grid3d/_grid3d.py +29 -0
- xtgeo/grid3d/_grid3d_fence.py +284 -0
- xtgeo/grid3d/_grid3d_utils.py +228 -0
- xtgeo/grid3d/_grid_boundary.py +76 -0
- xtgeo/grid3d/_grid_etc1.py +1683 -0
- xtgeo/grid3d/_grid_export.py +222 -0
- xtgeo/grid3d/_grid_hybrid.py +50 -0
- xtgeo/grid3d/_grid_import.py +79 -0
- xtgeo/grid3d/_grid_import_ecl.py +101 -0
- xtgeo/grid3d/_grid_import_roff.py +135 -0
- xtgeo/grid3d/_grid_import_xtgcpgeom.py +375 -0
- xtgeo/grid3d/_grid_refine.py +258 -0
- xtgeo/grid3d/_grid_roxapi.py +292 -0
- xtgeo/grid3d/_grid_translate_coords.py +154 -0
- xtgeo/grid3d/_grid_wellzone.py +165 -0
- xtgeo/grid3d/_gridprop_export.py +202 -0
- xtgeo/grid3d/_gridprop_import_eclrun.py +164 -0
- xtgeo/grid3d/_gridprop_import_grdecl.py +132 -0
- xtgeo/grid3d/_gridprop_import_roff.py +52 -0
- xtgeo/grid3d/_gridprop_import_xtgcpprop.py +168 -0
- xtgeo/grid3d/_gridprop_lowlevel.py +171 -0
- xtgeo/grid3d/_gridprop_op1.py +272 -0
- xtgeo/grid3d/_gridprop_roxapi.py +301 -0
- xtgeo/grid3d/_gridprop_value_init.py +140 -0
- xtgeo/grid3d/_gridprops_import_eclrun.py +344 -0
- xtgeo/grid3d/_gridprops_import_roff.py +83 -0
- xtgeo/grid3d/_roff_grid.py +470 -0
- xtgeo/grid3d/_roff_parameter.py +303 -0
- xtgeo/grid3d/grid.py +3010 -0
- xtgeo/grid3d/grid_properties.py +699 -0
- xtgeo/grid3d/grid_property.py +1313 -0
- xtgeo/grid3d/types.py +15 -0
- xtgeo/interfaces/rms/__init__.py +18 -0
- xtgeo/interfaces/rms/_regular_surface.py +460 -0
- xtgeo/interfaces/rms/_rms_base.py +100 -0
- xtgeo/interfaces/rms/_rmsapi_package.py +69 -0
- xtgeo/interfaces/rms/rmsapi_utils.py +438 -0
- xtgeo/io/__init__.py +1 -0
- xtgeo/io/_file.py +603 -0
- xtgeo/metadata/__init__.py +17 -0
- xtgeo/metadata/metadata.py +435 -0
- xtgeo/roxutils/__init__.py +7 -0
- xtgeo/roxutils/_roxar_loader.py +54 -0
- xtgeo/roxutils/_roxutils_etc.py +122 -0
- xtgeo/roxutils/roxutils.py +207 -0
- xtgeo/surface/__init__.py +20 -0
- xtgeo/surface/_regsurf_boundary.py +26 -0
- xtgeo/surface/_regsurf_cube.py +210 -0
- xtgeo/surface/_regsurf_cube_window.py +391 -0
- xtgeo/surface/_regsurf_cube_window_v2.py +297 -0
- xtgeo/surface/_regsurf_cube_window_v3.py +360 -0
- xtgeo/surface/_regsurf_export.py +388 -0
- xtgeo/surface/_regsurf_grid3d.py +275 -0
- xtgeo/surface/_regsurf_gridding.py +347 -0
- xtgeo/surface/_regsurf_ijxyz_parser.py +278 -0
- xtgeo/surface/_regsurf_import.py +347 -0
- xtgeo/surface/_regsurf_lowlevel.py +122 -0
- xtgeo/surface/_regsurf_oper.py +538 -0
- xtgeo/surface/_regsurf_utils.py +81 -0
- xtgeo/surface/_surfs_import.py +43 -0
- xtgeo/surface/_zmap_parser.py +138 -0
- xtgeo/surface/regular_surface.py +3043 -0
- xtgeo/surface/surfaces.py +276 -0
- xtgeo/well/__init__.py +24 -0
- xtgeo/well/_blockedwell_roxapi.py +241 -0
- xtgeo/well/_blockedwells_roxapi.py +68 -0
- xtgeo/well/_well_aux.py +30 -0
- xtgeo/well/_well_io.py +327 -0
- xtgeo/well/_well_oper.py +483 -0
- xtgeo/well/_well_roxapi.py +304 -0
- xtgeo/well/_wellmarkers.py +486 -0
- xtgeo/well/_wells_utils.py +158 -0
- xtgeo/well/blocked_well.py +220 -0
- xtgeo/well/blocked_wells.py +134 -0
- xtgeo/well/well1.py +1516 -0
- xtgeo/well/wells.py +211 -0
- xtgeo/xyz/__init__.py +6 -0
- xtgeo/xyz/_polygons_oper.py +272 -0
- xtgeo/xyz/_xyz.py +758 -0
- xtgeo/xyz/_xyz_data.py +646 -0
- xtgeo/xyz/_xyz_io.py +737 -0
- xtgeo/xyz/_xyz_lowlevel.py +42 -0
- xtgeo/xyz/_xyz_oper.py +613 -0
- xtgeo/xyz/_xyz_roxapi.py +766 -0
- xtgeo/xyz/points.py +698 -0
- xtgeo/xyz/polygons.py +827 -0
- xtgeo-4.14.1.dist-info/METADATA +146 -0
- xtgeo-4.14.1.dist-info/RECORD +122 -0
- xtgeo-4.14.1.dist-info/WHEEL +5 -0
- xtgeo-4.14.1.dist-info/licenses/LICENSE.md +165 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""Do gridding from 3D parameters"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import warnings
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import numpy.ma as ma
|
|
10
|
+
import scipy.interpolate
|
|
11
|
+
import scipy.ndimage
|
|
12
|
+
|
|
13
|
+
from xtgeo.common.constants import UNDEF, UNDEF_LIMIT
|
|
14
|
+
from xtgeo.common.log import null_logger
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
|
|
19
|
+
from xtgeo.surface import RegularSurface
|
|
20
|
+
|
|
21
|
+
logger = null_logger(__name__)
|
|
22
|
+
|
|
23
|
+
# Note: 'self' is an instance of RegularSurface
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def points_gridding(self, points, method="linear", coarsen=1):
|
|
27
|
+
"""Do gridding from a points data set."""
|
|
28
|
+
|
|
29
|
+
xiv, yiv = self.get_xy_values()
|
|
30
|
+
|
|
31
|
+
dfra = points.get_dataframe(copy=False)
|
|
32
|
+
|
|
33
|
+
xcv = dfra[points.xname].values
|
|
34
|
+
ycv = dfra[points.yname].values
|
|
35
|
+
zcv = dfra[points.zname].values
|
|
36
|
+
|
|
37
|
+
if coarsen > 1:
|
|
38
|
+
xcv = xcv[::coarsen]
|
|
39
|
+
ycv = ycv[::coarsen]
|
|
40
|
+
zcv = zcv[::coarsen]
|
|
41
|
+
|
|
42
|
+
validmethods = ["linear", "nearest", "cubic"]
|
|
43
|
+
if method not in set(validmethods):
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Invalid method for gridding: {method}, valid options are {validmethods}"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
znew = scipy.interpolate.griddata(
|
|
50
|
+
(xcv, ycv), zcv, (xiv, yiv), method=method, fill_value=np.nan
|
|
51
|
+
)
|
|
52
|
+
except ValueError as verr:
|
|
53
|
+
raise RuntimeError(f"Could not do gridding: {verr}")
|
|
54
|
+
|
|
55
|
+
logger.info("Gridding point ... DONE")
|
|
56
|
+
|
|
57
|
+
self._ensure_correct_values(znew)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def avgsum_from_3dprops_gridding(
|
|
61
|
+
self,
|
|
62
|
+
summing=False,
|
|
63
|
+
xprop=None,
|
|
64
|
+
yprop=None,
|
|
65
|
+
mprop=None,
|
|
66
|
+
dzprop=None,
|
|
67
|
+
truncate_le=None,
|
|
68
|
+
zoneprop=None,
|
|
69
|
+
zone_minmax=None,
|
|
70
|
+
coarsen=1,
|
|
71
|
+
zone_avg=False,
|
|
72
|
+
mask_outside=False,
|
|
73
|
+
):
|
|
74
|
+
"""Get surface average from a 3D grid prop."""
|
|
75
|
+
# NOTE:
|
|
76
|
+
# This do _either_ averaging _or_ sum gridding (if summing is True)
|
|
77
|
+
# - Inputs shall be pure 3D numpies, not masked!
|
|
78
|
+
# - Xprop and yprop must be made for all cells
|
|
79
|
+
# - Also dzprop for all cells, and dzprop = 0 for inactive cells!
|
|
80
|
+
|
|
81
|
+
logger.info("Avgsum calculation %s", __name__)
|
|
82
|
+
|
|
83
|
+
if zone_minmax is None:
|
|
84
|
+
raise ValueError("zone_minmax is required")
|
|
85
|
+
|
|
86
|
+
if dzprop is None:
|
|
87
|
+
raise ValueError("DZ property is required")
|
|
88
|
+
|
|
89
|
+
xprop, yprop, zoneprop, mprop, dzprop = _zone_averaging(
|
|
90
|
+
xprop,
|
|
91
|
+
yprop,
|
|
92
|
+
zoneprop,
|
|
93
|
+
zone_minmax,
|
|
94
|
+
coarsen,
|
|
95
|
+
zone_avg,
|
|
96
|
+
dzprop,
|
|
97
|
+
mprop,
|
|
98
|
+
summing=summing,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
gnlay = xprop.shape[2]
|
|
102
|
+
|
|
103
|
+
# avoid artifacts from inactive cells that slips through somehow...(?)
|
|
104
|
+
if dzprop.max() > UNDEF_LIMIT:
|
|
105
|
+
raise RuntimeError("Bug: DZ with unphysical values present")
|
|
106
|
+
|
|
107
|
+
trimbydz = False
|
|
108
|
+
if not summing:
|
|
109
|
+
trimbydz = True
|
|
110
|
+
|
|
111
|
+
if summing and mask_outside:
|
|
112
|
+
trimbydz = True
|
|
113
|
+
|
|
114
|
+
xiv, yiv = self.get_xy_values()
|
|
115
|
+
|
|
116
|
+
# weight are needed if zoneprop is not follow layers, but rather regions
|
|
117
|
+
weights = dzprop.copy() * 0.0 + 1.0
|
|
118
|
+
weights[zoneprop < zone_minmax[0]] = 0.0
|
|
119
|
+
weights[zoneprop > zone_minmax[1]] = 0.0
|
|
120
|
+
|
|
121
|
+
# this operation is needed if zoneprop is aka a region ("irregular zone")
|
|
122
|
+
zoneprop = ma.masked_less(zoneprop, zone_minmax[0])
|
|
123
|
+
zoneprop = ma.masked_greater(zoneprop, zone_minmax[1])
|
|
124
|
+
|
|
125
|
+
for klay0 in range(gnlay):
|
|
126
|
+
k1lay = klay0 + 1
|
|
127
|
+
|
|
128
|
+
if k1lay == 1:
|
|
129
|
+
msum = np.zeros((self.ncol, self.nrow), order="C")
|
|
130
|
+
dzsum = np.zeros((self.ncol, self.nrow), order="C")
|
|
131
|
+
|
|
132
|
+
numz = zoneprop[::, ::, klay0].mean()
|
|
133
|
+
if isinstance(numz, float):
|
|
134
|
+
numz = int(round(zoneprop[::, ::, klay0].mean()))
|
|
135
|
+
if numz < zone_minmax[0] or numz > zone_minmax[1]:
|
|
136
|
+
continue
|
|
137
|
+
else:
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
qmcompute = True
|
|
141
|
+
if summing:
|
|
142
|
+
propsum = mprop[:, :, klay0].sum()
|
|
143
|
+
if abs(propsum) < 1e-12:
|
|
144
|
+
logger.info("Too little HC, skip layer K = %s", k1lay)
|
|
145
|
+
qmcompute = False
|
|
146
|
+
else:
|
|
147
|
+
logger.debug("Z property sum is %s", propsum)
|
|
148
|
+
|
|
149
|
+
logger.info("Mapping for layer or zone %s ....", k1lay)
|
|
150
|
+
|
|
151
|
+
xcv = xprop[::, ::, klay0].ravel(order="C")
|
|
152
|
+
ycv = yprop[::, ::, klay0].ravel(order="C")
|
|
153
|
+
mvv = mprop[::, ::, klay0].ravel(order="C")
|
|
154
|
+
dzv = dzprop[::, ::, klay0].ravel(order="C")
|
|
155
|
+
wei = weights[::, ::, klay0].ravel(order="C")
|
|
156
|
+
|
|
157
|
+
# this is done to avoid problems if undef values still remains
|
|
158
|
+
# in the coordinates (assume Y undef where X undef):
|
|
159
|
+
xcc = xcv.copy()
|
|
160
|
+
xcv = xcv[xcc < 1e20]
|
|
161
|
+
ycv = ycv[xcc < 1e20]
|
|
162
|
+
mvv = mvv[xcc < 1e20]
|
|
163
|
+
dzv = dzv[xcc < 1e20]
|
|
164
|
+
wei = wei[xcc < 1e20]
|
|
165
|
+
|
|
166
|
+
mvdz = mvv * wei if summing else mvv * dzv * wei
|
|
167
|
+
|
|
168
|
+
if qmcompute:
|
|
169
|
+
try:
|
|
170
|
+
mvdzi = scipy.interpolate.griddata(
|
|
171
|
+
(xcv, ycv), mvdz, (xiv, yiv), method="linear", fill_value=0.0
|
|
172
|
+
)
|
|
173
|
+
except ValueError:
|
|
174
|
+
warnings.warn("Some problems in gridding ... will contue", UserWarning)
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
msum = msum + mvdzi
|
|
178
|
+
|
|
179
|
+
if trimbydz:
|
|
180
|
+
try:
|
|
181
|
+
dzi = scipy.interpolate.griddata(
|
|
182
|
+
(xcv, ycv), dzv, (xiv, yiv), method="linear", fill_value=0.0
|
|
183
|
+
)
|
|
184
|
+
except ValueError:
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
dzsum = dzsum + dzi
|
|
188
|
+
|
|
189
|
+
if not summing:
|
|
190
|
+
dzsum[dzsum == 0.0] = 1e-20
|
|
191
|
+
vvz = msum / dzsum
|
|
192
|
+
vvz = ma.masked_invalid(vvz)
|
|
193
|
+
else:
|
|
194
|
+
vvz = msum
|
|
195
|
+
|
|
196
|
+
vvz = (
|
|
197
|
+
ma.masked_where(dzsum < 1.1e-20, vvz) if trimbydz else ma.array(vvz)
|
|
198
|
+
) # so the result becomes a ma array
|
|
199
|
+
|
|
200
|
+
if truncate_le:
|
|
201
|
+
vvz = ma.masked_less(vvz, truncate_le)
|
|
202
|
+
|
|
203
|
+
self.values = vvz
|
|
204
|
+
logger.info("Avgsum calculation done! %s", __name__)
|
|
205
|
+
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _zone_averaging(
|
|
210
|
+
xprop, yprop, zoneprop, zone_minmax, coarsen, zone_avg, dzprop, mprop, summing=False
|
|
211
|
+
):
|
|
212
|
+
# General preprocessing, and...
|
|
213
|
+
# Change the 3D numpy array so they get layers by
|
|
214
|
+
# averaging across zones. This may speed up a lot,
|
|
215
|
+
# but will reduce the resolution.
|
|
216
|
+
# The x y coordinates shall be averaged (ideally
|
|
217
|
+
# with thickness weighting...) while e.g. hcpfzprop
|
|
218
|
+
# must be summed.
|
|
219
|
+
# Somewhat different processing whether this is a hc thickness
|
|
220
|
+
# or an average.
|
|
221
|
+
|
|
222
|
+
xpr = xprop
|
|
223
|
+
ypr = yprop
|
|
224
|
+
zpr = zoneprop
|
|
225
|
+
dpr = dzprop
|
|
226
|
+
|
|
227
|
+
mpr = mprop
|
|
228
|
+
|
|
229
|
+
if coarsen > 1:
|
|
230
|
+
xpr = xprop[::coarsen, ::coarsen, ::].copy(order="C")
|
|
231
|
+
ypr = yprop[::coarsen, ::coarsen, ::].copy(order="C")
|
|
232
|
+
zpr = zoneprop[::coarsen, ::coarsen, ::].copy(order="C")
|
|
233
|
+
dpr = dzprop[::coarsen, ::coarsen, ::].copy(order="C")
|
|
234
|
+
mpr = mprop[::coarsen, ::coarsen, ::].copy(order="C")
|
|
235
|
+
zpr.astype(np.int32)
|
|
236
|
+
|
|
237
|
+
if zone_avg:
|
|
238
|
+
zmin = int(zone_minmax[0])
|
|
239
|
+
zmax = int(zone_minmax[1])
|
|
240
|
+
if zpr.min() > zmin:
|
|
241
|
+
zmin = zpr.min()
|
|
242
|
+
if zpr.max() < zmax:
|
|
243
|
+
zmax = zpr.max()
|
|
244
|
+
|
|
245
|
+
newx = []
|
|
246
|
+
newy = []
|
|
247
|
+
newz = []
|
|
248
|
+
newm = []
|
|
249
|
+
newd = []
|
|
250
|
+
|
|
251
|
+
for izv in range(zmin, zmax + 1):
|
|
252
|
+
logger.info("Averaging for zone %s ...", izv)
|
|
253
|
+
xpr2 = ma.masked_where(zpr != izv, xpr)
|
|
254
|
+
ypr2 = ma.masked_where(zpr != izv, ypr)
|
|
255
|
+
zpr2 = ma.masked_where(zpr != izv, zpr)
|
|
256
|
+
dpr2 = ma.masked_where(zpr != izv, dpr)
|
|
257
|
+
mpr2 = ma.masked_where(zpr != izv, mpr)
|
|
258
|
+
|
|
259
|
+
# get the thickness and normalize along axis 2 (vertical)
|
|
260
|
+
# to get normalized thickness weights
|
|
261
|
+
lay_sums = dpr2.sum(axis=2)
|
|
262
|
+
normed_dz = dpr2 / lay_sums[:, :, np.newaxis]
|
|
263
|
+
|
|
264
|
+
# assume that coordinates have equal weights within a zone
|
|
265
|
+
xpr2 = ma.average(xpr2, axis=2)
|
|
266
|
+
ypr2 = ma.average(ypr2, axis=2)
|
|
267
|
+
zpr2 = ma.average(zpr2, axis=2) # avg zone
|
|
268
|
+
|
|
269
|
+
dpr2 = ma.sum(dpr2, axis=2)
|
|
270
|
+
|
|
271
|
+
if summing:
|
|
272
|
+
mpr2 = ma.sum(mpr2, axis=2)
|
|
273
|
+
else:
|
|
274
|
+
mpr2 = ma.average(mpr2, weights=normed_dz, axis=2) # avg zone
|
|
275
|
+
|
|
276
|
+
newx.append(xpr2)
|
|
277
|
+
newy.append(ypr2)
|
|
278
|
+
newz.append(zpr2)
|
|
279
|
+
newd.append(dpr2)
|
|
280
|
+
newm.append(mpr2)
|
|
281
|
+
|
|
282
|
+
xpr = ma.dstack(newx)
|
|
283
|
+
ypr = ma.dstack(newy)
|
|
284
|
+
zpr = ma.dstack(newz)
|
|
285
|
+
dpr = ma.dstack(newd)
|
|
286
|
+
mpr = ma.dstack(newm)
|
|
287
|
+
zpr.astype(np.int32)
|
|
288
|
+
|
|
289
|
+
xpr = ma.filled(xpr, fill_value=UNDEF)
|
|
290
|
+
ypr = ma.filled(ypr, fill_value=UNDEF)
|
|
291
|
+
zpr = ma.filled(zpr, fill_value=0)
|
|
292
|
+
dpr = ma.filled(dpr, fill_value=0.0)
|
|
293
|
+
|
|
294
|
+
mpr = ma.filled(mpr, fill_value=0.0)
|
|
295
|
+
|
|
296
|
+
return xpr, ypr, zpr, mpr, dpr
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def surf_fill(self, fill_value=None):
|
|
300
|
+
"""Replace the value of invalid 'data' cells (indicated by 'invalid')
|
|
301
|
+
by the value of the nearest valid data cell or a constant.
|
|
302
|
+
|
|
303
|
+
This is a quite fast method to fill undefined areas of the map.
|
|
304
|
+
The surface values are updated 'in-place'
|
|
305
|
+
|
|
306
|
+
.. versionadded:: 2.1
|
|
307
|
+
.. versionchanged:: 2.6 Added fill_value
|
|
308
|
+
"""
|
|
309
|
+
logger.info("Do fill...")
|
|
310
|
+
|
|
311
|
+
if fill_value is not None:
|
|
312
|
+
if np.isscalar(fill_value) and not isinstance(fill_value, str):
|
|
313
|
+
self.values = ma.filled(self.values, fill_value=float(fill_value))
|
|
314
|
+
else:
|
|
315
|
+
raise ValueError("Keyword fill_value must be int or float")
|
|
316
|
+
else:
|
|
317
|
+
invalid = ma.getmaskarray(self.values)
|
|
318
|
+
|
|
319
|
+
ind = scipy.ndimage.distance_transform_edt(
|
|
320
|
+
invalid, return_distances=False, return_indices=True
|
|
321
|
+
)
|
|
322
|
+
self._values = self._values[tuple(ind)]
|
|
323
|
+
logger.info("Do fill... DONE")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _smooth(
|
|
327
|
+
self: RegularSurface,
|
|
328
|
+
window_function: Callable[[np.ndarray], np.ndarray],
|
|
329
|
+
iterations: int = 1,
|
|
330
|
+
) -> None:
|
|
331
|
+
"""
|
|
332
|
+
Smooth a RegularSurface using a window function.
|
|
333
|
+
|
|
334
|
+
Original mask (undefined values) is stored before applying
|
|
335
|
+
smoothing on a filled array. Subsequently the original mask
|
|
336
|
+
is used to restore the undefined values in the output.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
mask = ma.getmaskarray(self.values)
|
|
340
|
+
|
|
341
|
+
self.fill()
|
|
342
|
+
|
|
343
|
+
smoothed_values = self.values
|
|
344
|
+
for _ in range(iterations):
|
|
345
|
+
smoothed_values = window_function(smoothed_values, mode="nearest")
|
|
346
|
+
|
|
347
|
+
self.values = ma.array(smoothed_values, mask=mask)
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""IJXYZ files (OW/DSG) parsing.
|
|
2
|
+
|
|
3
|
+
This format lacks explicit information such as origin, xinc, yinc, rotation etc; i.e.
|
|
4
|
+
all these setting must be computed from the input.
|
|
5
|
+
|
|
6
|
+
Missing inline and xline values are assumed to be undefined. Examples:
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
========================================================================================
|
|
10
|
+
815 1210 777574.220000 6736507.760000 1010.000000
|
|
11
|
+
816 1210 777561.894910 6736521.890000 1010.000000
|
|
12
|
+
817 1210 777549.569820 6736536.020000 1010.000000
|
|
13
|
+
818 1210 777537.244731 6736550.150000 1010.000000
|
|
14
|
+
819 1210 777524.919641 6736564.280000 1010.000000
|
|
15
|
+
820 1210 777512.594551 6736578.410000 1010.000000
|
|
16
|
+
821 1210 777500.269461 6736592.540000 1010.000000
|
|
17
|
+
822 1210 777487.944371 6736606.670000 1010.000000
|
|
18
|
+
823 1210 777475.619281 6736620.800000 1010.000000
|
|
19
|
+
...
|
|
20
|
+
846 1211 777201.561266 6736954.005898 1010.000000
|
|
21
|
+
847 1211 777189.236176 6736968.135898 1010.000000
|
|
22
|
+
848 1211 777176.911086 6736982.265898 1010.000000
|
|
23
|
+
849 1211 777164.585996 6736996.395898 1010.000000
|
|
24
|
+
850 1211 777152.260906 6737010.525898 1010.000000
|
|
25
|
+
851 1211 777139.935817 6737024.655898 1010.000000
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
Here inline is fastest and increasing and every inline and xline is applied
|
|
29
|
+
|
|
30
|
+
========================================================================================
|
|
31
|
+
|
|
32
|
+
24 3 774906.625000 6736030.500000 1741.107300
|
|
33
|
+
26 3 774946.017310 6736023.554073 1730.530884
|
|
34
|
+
28 3 774985.409620 6736016.608146 1719.947876
|
|
35
|
+
30 3 775024.801930 6736009.662219 1709.229370
|
|
36
|
+
32 3 775064.194240 6736002.716292 1698.470337
|
|
37
|
+
34 3 775103.586551 6735995.770364 1687.320435
|
|
38
|
+
...
|
|
39
|
+
272 3 779791.271455 6735169.205039 1404.342041
|
|
40
|
+
274 3 779830.663765 6735162.259112 1413.154175
|
|
41
|
+
24 6 774915.307409 6736079.740388 1727.455078
|
|
42
|
+
26 6 774954.699719 6736072.794461 1719.031128
|
|
43
|
+
28 6 774994.092029 6736065.848533 1710.606201
|
|
44
|
+
30 6 775033.484339 6736058.902606 1702.161255
|
|
45
|
+
32 6 775072.876649 6736051.956679 1693.707153
|
|
46
|
+
34 6 775112.268959 6736045.010752 1685.164673
|
|
47
|
+
|
|
48
|
+
Here every second inline and every third xline is applied
|
|
49
|
+
|
|
50
|
+
========================================================================================
|
|
51
|
+
|
|
52
|
+
1312 2015 655905.938642 6627044.343360 1671.420800
|
|
53
|
+
1312 2010 655858.839186 6627003.259513 1669.091400
|
|
54
|
+
1302 2028 656151.649337 6627009.862811 1680.437500
|
|
55
|
+
1312 2011 655868.259077 6627011.476282 1669.444000
|
|
56
|
+
1302 2029 656161.069228 6627018.079581 1681.000000
|
|
57
|
+
1302 2026 656132.809555 6626993.429272 1679.312500
|
|
58
|
+
1302 2027 656142.229446 6627001.646042 1679.875000
|
|
59
|
+
1302 2024 656113.969773 6626976.995733 1678.187500
|
|
60
|
+
1302 2025 656123.389664 6626985.212503 1678.750000
|
|
61
|
+
|
|
62
|
+
This seems to lack obvious patterns (mostly due to many undefied cells?). Hence we
|
|
63
|
+
need to detect minimum spacing and min/max of both ilines and xlines, unless a template
|
|
64
|
+
is applied.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
from __future__ import annotations
|
|
68
|
+
|
|
69
|
+
from dataclasses import dataclass, field
|
|
70
|
+
from typing import TYPE_CHECKING, Literal, Optional
|
|
71
|
+
|
|
72
|
+
import numpy as np
|
|
73
|
+
from numpy.ma import MaskedArray
|
|
74
|
+
|
|
75
|
+
from xtgeo.common.calc import find_flip, xyori_from_ij
|
|
76
|
+
from xtgeo.common.log import null_logger
|
|
77
|
+
|
|
78
|
+
if TYPE_CHECKING:
|
|
79
|
+
from xtgeo.cube.cube1 import Cube
|
|
80
|
+
|
|
81
|
+
from .regular_surface import RegularSurface
|
|
82
|
+
|
|
83
|
+
logger = null_logger(__name__)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class SurfaceIJXYZ:
|
|
88
|
+
"""Temporary class for parsing IJXYZ settings.
|
|
89
|
+
|
|
90
|
+
Attributes:
|
|
91
|
+
xcoords_in: Input X coordinates.
|
|
92
|
+
ycoords_in: Input Y coordinates.
|
|
93
|
+
values_in: Input values.
|
|
94
|
+
ilines_in: Input inline values.
|
|
95
|
+
xlines_in: Input crossline values.
|
|
96
|
+
template: Geometrical template.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
xcoords_in: MaskedArray
|
|
100
|
+
ycoords_in: MaskedArray
|
|
101
|
+
values_in: MaskedArray
|
|
102
|
+
ilines_in: np.ndarray
|
|
103
|
+
xlines_in: np.ndarray
|
|
104
|
+
template: Optional[RegularSurface | Cube] = None
|
|
105
|
+
|
|
106
|
+
ncol: int = field(default=1, init=False)
|
|
107
|
+
nrow: int = field(default=1, init=False)
|
|
108
|
+
xori: float = field(default=0.0, init=False)
|
|
109
|
+
yori: float = field(default=0.0, init=False)
|
|
110
|
+
xinc: float = field(default=1.0, init=False)
|
|
111
|
+
yinc: float = field(default=1.0, init=False)
|
|
112
|
+
yflip: Literal[-1, 1] = field(default=1, init=False)
|
|
113
|
+
xcoords: MaskedArray = field(default_factory=MaskedArray, init=False)
|
|
114
|
+
ycoords: MaskedArray = field(default_factory=MaskedArray, init=False)
|
|
115
|
+
values: MaskedArray = field(default_factory=MaskedArray, init=False)
|
|
116
|
+
ilines: np.ndarray = field(default_factory=lambda: np.zeros(1), init=False)
|
|
117
|
+
xlines: np.ndarray = field(default_factory=lambda: np.zeros(1), init=False)
|
|
118
|
+
|
|
119
|
+
def __post_init__(self) -> None:
|
|
120
|
+
self._parse_arrays()
|
|
121
|
+
if self.template:
|
|
122
|
+
self._map_on_template()
|
|
123
|
+
else:
|
|
124
|
+
self._calculate_geometrics()
|
|
125
|
+
|
|
126
|
+
def _parse_arrays(self) -> None:
|
|
127
|
+
"""Parse input and arrange basic data and arrays."""
|
|
128
|
+
|
|
129
|
+
logger.debug("Parse ijxyz arrays")
|
|
130
|
+
# both ilines and xlines may jump e.g. every second; detect minimum jump:
|
|
131
|
+
inline_mindiff = int(np.min(np.diff(np.unique(np.sort(self.ilines_in)))))
|
|
132
|
+
xline_mindiff = int(np.min(np.diff(np.unique(np.sort(self.xlines_in)))))
|
|
133
|
+
|
|
134
|
+
i_index = np.divide(self.ilines_in, inline_mindiff)
|
|
135
|
+
i_index = (i_index - i_index.min()).astype("int32")
|
|
136
|
+
|
|
137
|
+
j_index = np.divide(self.xlines_in, xline_mindiff)
|
|
138
|
+
j_index = (j_index - j_index.min()).astype("int32")
|
|
139
|
+
|
|
140
|
+
self.ncol = int(i_index.max() + 1)
|
|
141
|
+
self.nrow = int(j_index.max() + 1)
|
|
142
|
+
|
|
143
|
+
self.ilines = np.array(
|
|
144
|
+
range(
|
|
145
|
+
self.ilines_in.min(),
|
|
146
|
+
self.ilines_in.max() + inline_mindiff,
|
|
147
|
+
inline_mindiff,
|
|
148
|
+
)
|
|
149
|
+
).astype("int32")
|
|
150
|
+
|
|
151
|
+
self.xlines = np.array(
|
|
152
|
+
range(
|
|
153
|
+
self.xlines_in.min(),
|
|
154
|
+
self.xlines_in.max() + xline_mindiff,
|
|
155
|
+
xline_mindiff,
|
|
156
|
+
)
|
|
157
|
+
).astype("int32")
|
|
158
|
+
|
|
159
|
+
shape = (self.ncol, self.nrow)
|
|
160
|
+
|
|
161
|
+
xvalues = np.full(shape, np.nan)
|
|
162
|
+
xvalues[i_index, j_index] = self.xcoords_in
|
|
163
|
+
self.xcoords = np.ma.masked_invalid(xvalues)
|
|
164
|
+
|
|
165
|
+
yvalues = np.full(shape, np.nan)
|
|
166
|
+
yvalues[i_index, j_index] = self.ycoords_in
|
|
167
|
+
self.ycoords = np.ma.masked_invalid(yvalues)
|
|
168
|
+
|
|
169
|
+
zvalues = np.full(shape, np.nan)
|
|
170
|
+
zvalues[i_index, j_index] = self.values_in
|
|
171
|
+
self.values = np.ma.masked_invalid(zvalues)
|
|
172
|
+
|
|
173
|
+
def _map_on_template(self) -> None:
|
|
174
|
+
"""An existing RegularSurface or Cube forms the geometrical template."""
|
|
175
|
+
logger.debug("Parse ijxyz with template")
|
|
176
|
+
|
|
177
|
+
# TODO: Remove these when moved to xtgeo.io
|
|
178
|
+
from xtgeo.cube.cube1 import Cube
|
|
179
|
+
|
|
180
|
+
from .regular_surface import RegularSurface
|
|
181
|
+
|
|
182
|
+
if not isinstance(self.template, (RegularSurface, Cube)):
|
|
183
|
+
raise ValueError(
|
|
184
|
+
"The provided template is not of type RegularSurface or Cube"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
ilines_orig = self.ilines.copy()
|
|
188
|
+
xlines_orig = self.xlines.copy()
|
|
189
|
+
|
|
190
|
+
il_step = int(np.diff(np.sort(ilines_orig)).min())
|
|
191
|
+
xl_step = int(np.diff(np.sort(xlines_orig)).min())
|
|
192
|
+
|
|
193
|
+
# now ovveride with settings from template
|
|
194
|
+
for attr in (
|
|
195
|
+
"ncol",
|
|
196
|
+
"nrow",
|
|
197
|
+
"xori",
|
|
198
|
+
"yori",
|
|
199
|
+
"xinc",
|
|
200
|
+
"yinc",
|
|
201
|
+
"rotation",
|
|
202
|
+
"yflip",
|
|
203
|
+
"ilines",
|
|
204
|
+
"xlines",
|
|
205
|
+
):
|
|
206
|
+
setattr(self, attr, getattr(self.template, attr))
|
|
207
|
+
|
|
208
|
+
i_start = abs(int((self.ilines.min() - ilines_orig.min()) / il_step))
|
|
209
|
+
j_start = abs(int((self.xlines.min() - xlines_orig.min()) / xl_step))
|
|
210
|
+
|
|
211
|
+
shape = (self.ncol, self.nrow)
|
|
212
|
+
|
|
213
|
+
zvalues = np.full(shape, np.nan)
|
|
214
|
+
zvalues[
|
|
215
|
+
i_start : i_start + self.values.shape[0],
|
|
216
|
+
j_start : j_start + self.values.shape[1],
|
|
217
|
+
] = np.ma.filled(self.values, fill_value=np.nan)
|
|
218
|
+
|
|
219
|
+
self.values = np.ma.masked_invalid(zvalues)
|
|
220
|
+
|
|
221
|
+
def _calculate_geometrics(self) -> None:
|
|
222
|
+
"""Compute the geometrics such as rotation, flip origin and increments."""
|
|
223
|
+
|
|
224
|
+
logger.debug("Compute ijxyz geometrics from arrays")
|
|
225
|
+
# along_east and along_north here refers to the case when rotation is 0.0
|
|
226
|
+
dx_along_east = np.diff(self.xcoords, axis=0)
|
|
227
|
+
dy_along_east = np.diff(self.ycoords, axis=0)
|
|
228
|
+
dx_along_north = np.diff(self.xcoords, axis=1)
|
|
229
|
+
dy_along_north = np.diff(self.ycoords, axis=1)
|
|
230
|
+
|
|
231
|
+
self.yflip = find_flip(
|
|
232
|
+
(dx_along_east.mean(), dy_along_east.mean(), 0.0),
|
|
233
|
+
(dx_along_north.mean(), dy_along_north.mean(), 0.0),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
self.rotation = float(
|
|
237
|
+
np.degrees(np.arctan2(dy_along_east, dx_along_east)).mean()
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
distances_xinc = np.sqrt(dx_along_east**2 + dy_along_east**2)
|
|
241
|
+
distances_yinc = np.sqrt(dx_along_north**2 + dy_along_north**2)
|
|
242
|
+
self.xinc = float(distances_xinc.mean())
|
|
243
|
+
self.yinc = float(distances_yinc.mean())
|
|
244
|
+
|
|
245
|
+
cols, rows = np.ma.where(~self.xcoords.mask) # get active indices
|
|
246
|
+
|
|
247
|
+
first_active_col = int(cols[0])
|
|
248
|
+
first_active_row = int(rows[0])
|
|
249
|
+
|
|
250
|
+
self.xori, self.yori = xyori_from_ij(
|
|
251
|
+
first_active_col,
|
|
252
|
+
first_active_row,
|
|
253
|
+
self.xcoords[first_active_col, first_active_row],
|
|
254
|
+
self.ycoords[first_active_col, first_active_row],
|
|
255
|
+
self.xinc,
|
|
256
|
+
self.yinc,
|
|
257
|
+
self.ncol,
|
|
258
|
+
self.nrow,
|
|
259
|
+
self.yflip,
|
|
260
|
+
float(self.rotation),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def parse_ijxyz(mfile, template=None) -> tuple:
|
|
265
|
+
"""Read IJXYZ data file and parse the content."""
|
|
266
|
+
|
|
267
|
+
if mfile.memstream:
|
|
268
|
+
mfile.file.seek(0)
|
|
269
|
+
|
|
270
|
+
data = np.loadtxt(mfile.file, comments=["@", "#", "EOB"])
|
|
271
|
+
|
|
272
|
+
inline = data[:, 0].astype("int32")
|
|
273
|
+
xline = data[:, 1].astype("int32")
|
|
274
|
+
x_arr = data[:, 2].astype("float64")
|
|
275
|
+
y_arr = data[:, 3].astype("float64")
|
|
276
|
+
z_arr = data[:, 4].astype("float64")
|
|
277
|
+
|
|
278
|
+
return SurfaceIJXYZ(x_arr, y_arr, z_arr, inline, xline, template=template)
|