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
xtgeo/cube/cube1.py
ADDED
|
@@ -0,0 +1,1023 @@
|
|
|
1
|
+
"""Module for a seismic (or whatever) cube."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numbers
|
|
6
|
+
import warnings
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from xtgeo.common.constants import UNDEF
|
|
12
|
+
from xtgeo.common.exceptions import InvalidFileFormatError
|
|
13
|
+
from xtgeo.common.log import null_logger
|
|
14
|
+
from xtgeo.common.sys import generic_hash
|
|
15
|
+
from xtgeo.common.types import Dimensions
|
|
16
|
+
from xtgeo.common.xtgeo_dialog import XTGDescription
|
|
17
|
+
from xtgeo.grid3d.grid import grid_from_cube
|
|
18
|
+
from xtgeo.io._file import FileFormat, FileWrapper
|
|
19
|
+
from xtgeo.metadata.metadata import MetaDataRegularCube
|
|
20
|
+
from xtgeo.xyz.polygons import Polygons
|
|
21
|
+
|
|
22
|
+
from . import (
|
|
23
|
+
_cube_export,
|
|
24
|
+
_cube_import,
|
|
25
|
+
_cube_roxapi,
|
|
26
|
+
_cube_utils,
|
|
27
|
+
_cube_window_attributes,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from xtgeo.surface.regular_surface import RegularSurface
|
|
32
|
+
|
|
33
|
+
logger = null_logger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _data_reader_factory(fmt: FileFormat):
|
|
37
|
+
if fmt == FileFormat.SEGY:
|
|
38
|
+
return _cube_import.import_segy
|
|
39
|
+
if fmt == FileFormat.STORM:
|
|
40
|
+
return _cube_import.import_stormcube
|
|
41
|
+
if fmt == FileFormat.XTG:
|
|
42
|
+
return _cube_import.import_xtgregcube
|
|
43
|
+
|
|
44
|
+
extensions = FileFormat.extensions_string(
|
|
45
|
+
[FileFormat.SEGY, FileFormat.STORM, FileFormat.XTG]
|
|
46
|
+
)
|
|
47
|
+
raise InvalidFileFormatError(
|
|
48
|
+
f"File format {fmt} is invalid for type Cube. "
|
|
49
|
+
f"Supported formats are {extensions}."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def cube_from_file(mfile, fformat="guess"):
|
|
54
|
+
"""This makes an instance of a Cube directly from file import.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
mfile (str): Name of file
|
|
58
|
+
fformat (str): See :meth:`Cube.from_file`
|
|
59
|
+
|
|
60
|
+
Example::
|
|
61
|
+
|
|
62
|
+
>>> import xtgeo
|
|
63
|
+
>>> mycube = xtgeo.cube_from_file(cube_dir + "/ib_test_cube2.segy")
|
|
64
|
+
"""
|
|
65
|
+
return Cube._read_file(mfile, fformat)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def cube_from_roxar(project, name, folder=None):
|
|
69
|
+
"""This makes an instance of a Cube directly from roxar input.
|
|
70
|
+
|
|
71
|
+
The folder is a string on form "a" or "a/b" if subfolders are present
|
|
72
|
+
|
|
73
|
+
Example::
|
|
74
|
+
|
|
75
|
+
import xtgeo
|
|
76
|
+
mycube = xtgeo.cube_from_roxar(project, "DepthCube")
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
# this is certainly hackish, and shall be rewritten to a proper class method
|
|
80
|
+
obj = Cube(ncol=9, nrow=9, nlay=9, xinc=9.99, yinc=9.99, zinc=9.99) # dummy
|
|
81
|
+
_cube_roxapi.import_cube_roxapi(obj, project, name, folder=folder)
|
|
82
|
+
obj._metadata.required = obj
|
|
83
|
+
return obj
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Cube:
|
|
87
|
+
"""Class for a (seismic) cube in the XTGeo framework.
|
|
88
|
+
|
|
89
|
+
The values are stored as a 3D numpy array (4 bytes; float32 is default),
|
|
90
|
+
with internal C ordering (nlay fastest).
|
|
91
|
+
|
|
92
|
+
See :func:`xtgeo.cube_from_file` for importing cubes from e.g. segy files.
|
|
93
|
+
|
|
94
|
+
See also Cube section in documentation: docs/datamodel.rst
|
|
95
|
+
|
|
96
|
+
Examples::
|
|
97
|
+
|
|
98
|
+
>>> import xtgeo
|
|
99
|
+
>>> # a user defined cube:
|
|
100
|
+
>>> mycube = xtgeo.Cube(
|
|
101
|
+
... xori=100.0,
|
|
102
|
+
... yori=200.0,
|
|
103
|
+
... zori=150.0,
|
|
104
|
+
... ncol=40,
|
|
105
|
+
... nrow=30,
|
|
106
|
+
... nlay=10,
|
|
107
|
+
... rotation=30,
|
|
108
|
+
... values=0
|
|
109
|
+
... )
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
xori: Origin in Easting coordinate
|
|
113
|
+
yori: Origin in Northing coordinate
|
|
114
|
+
zori: Origin in Depth coordinate, where depth is positive down
|
|
115
|
+
ncol: Number of columns
|
|
116
|
+
nrow: Number of columns
|
|
117
|
+
nlay: Number of layers, starting from top
|
|
118
|
+
rotation: Cube rotation, X axis is applied and "school-wise" rotation,
|
|
119
|
+
anti-clock in degrees
|
|
120
|
+
values: Numpy array with shape (ncol, nrow, nlay), C order, np.float32
|
|
121
|
+
ilines: 1D numpy array with ncol elements, aka INLINES array, defaults to arange
|
|
122
|
+
xlines: 1D numpy array with nrow elements, aka XLINES array, defaults to arange
|
|
123
|
+
segyfile: Name of source segyfile if any
|
|
124
|
+
filesrc: String: Source file if any
|
|
125
|
+
yflip: Normally 1; if -1 Y axis is flipped --> from left-handed (1) to
|
|
126
|
+
right handed (-1). Right handed cubes are common.
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
ncol,
|
|
133
|
+
nrow,
|
|
134
|
+
nlay,
|
|
135
|
+
xinc,
|
|
136
|
+
yinc,
|
|
137
|
+
zinc,
|
|
138
|
+
xori=0.0,
|
|
139
|
+
yori=0.0,
|
|
140
|
+
zori=0.0,
|
|
141
|
+
yflip=1,
|
|
142
|
+
values=0.0,
|
|
143
|
+
rotation=0.0,
|
|
144
|
+
zflip=1,
|
|
145
|
+
ilines=None,
|
|
146
|
+
xlines=None,
|
|
147
|
+
traceidcodes=None,
|
|
148
|
+
segyfile=None,
|
|
149
|
+
filesrc=None,
|
|
150
|
+
):
|
|
151
|
+
"""Initiate a Cube instance."""
|
|
152
|
+
|
|
153
|
+
self._filesrc = filesrc
|
|
154
|
+
self._xori = xori
|
|
155
|
+
self._yori = yori
|
|
156
|
+
self._zori = zori
|
|
157
|
+
self._ncol = ncol
|
|
158
|
+
self._nrow = nrow
|
|
159
|
+
self._nlay = nlay
|
|
160
|
+
self._xinc = xinc
|
|
161
|
+
self._yinc = yinc
|
|
162
|
+
self._zinc = zinc
|
|
163
|
+
self._yflip = yflip
|
|
164
|
+
self._zflip = zflip # currently not in use
|
|
165
|
+
self._rotation = rotation
|
|
166
|
+
|
|
167
|
+
# input values can be "list-like" or scalar
|
|
168
|
+
self._values = self._ensure_correct_values(values)
|
|
169
|
+
|
|
170
|
+
if ilines is None:
|
|
171
|
+
self._ilines = ilines or np.array(range(1, self._ncol + 1), dtype=np.int32)
|
|
172
|
+
else:
|
|
173
|
+
self._ilines = ilines
|
|
174
|
+
if xlines is None:
|
|
175
|
+
self._xlines = np.array(range(1, self._nrow + 1), dtype=np.int32)
|
|
176
|
+
else:
|
|
177
|
+
self._xlines = xlines
|
|
178
|
+
if traceidcodes is None:
|
|
179
|
+
self._traceidcodes = np.ones((self._ncol, self._nrow), dtype=np.int32)
|
|
180
|
+
else:
|
|
181
|
+
self._traceidcodes = traceidcodes
|
|
182
|
+
self._segyfile = segyfile
|
|
183
|
+
self.undef = UNDEF
|
|
184
|
+
|
|
185
|
+
self._metadata = MetaDataRegularCube()
|
|
186
|
+
self._metadata.required = self
|
|
187
|
+
|
|
188
|
+
def __repr__(self):
|
|
189
|
+
"""The __repr__ method."""
|
|
190
|
+
avg = self.values.mean()
|
|
191
|
+
return (
|
|
192
|
+
f"{self.__class__} (ncol={self.ncol!r}, nrow={self.nrow!r}, "
|
|
193
|
+
f"nlay={self.nlay!r}, original file: {self._filesrc}), "
|
|
194
|
+
f"average {avg}, ID=<{id(self)}>"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def __str__(self):
|
|
198
|
+
"""The __str__ method for pretty print."""
|
|
199
|
+
return self.describe(flush=False)
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def metadata(self):
|
|
203
|
+
"""Return metadata object instance of type MetaDataRegularSurface."""
|
|
204
|
+
return self._metadata
|
|
205
|
+
|
|
206
|
+
@metadata.setter
|
|
207
|
+
def metadata(self, obj):
|
|
208
|
+
# The current metadata object can be replaced. This is a bit dangerous so
|
|
209
|
+
# further check must be done to validate. TODO.
|
|
210
|
+
if not isinstance(obj, MetaDataRegularCube):
|
|
211
|
+
raise ValueError("Input obj not an instance of MetaDataRegularCube")
|
|
212
|
+
|
|
213
|
+
self._metadata = obj # checking is currently missing! TODO
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def ncol(self):
|
|
217
|
+
"""The NCOL (NX or I dir) number (read-only)."""
|
|
218
|
+
return self._ncol
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def nrow(self):
|
|
222
|
+
"""The NROW (NY or J dir) number (read-only)."""
|
|
223
|
+
return self._nrow
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def nlay(self):
|
|
227
|
+
"""The NLAY (or NZ or K dir) number (read-only)."""
|
|
228
|
+
return self._nlay
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def dimensions(self):
|
|
232
|
+
"""NamedTuple: The cube dimensions with 3 integers (read only)."""
|
|
233
|
+
return Dimensions(self._ncol, self._nrow, self._nlay)
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def xori(self):
|
|
237
|
+
"""The XORI (origin corner) coordinate."""
|
|
238
|
+
return self._xori
|
|
239
|
+
|
|
240
|
+
@xori.setter
|
|
241
|
+
def xori(self, val):
|
|
242
|
+
logger.warning("Changing xori is risky!")
|
|
243
|
+
self._xori = val
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def yori(self):
|
|
247
|
+
"""The YORI (origin corner) coordinate."""
|
|
248
|
+
return self._yori
|
|
249
|
+
|
|
250
|
+
@yori.setter
|
|
251
|
+
def yori(self, val):
|
|
252
|
+
logger.warning("Changing yori is risky!")
|
|
253
|
+
self._yori = val
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def zori(self):
|
|
257
|
+
"""The ZORI (origin corner) coordinate."""
|
|
258
|
+
return self._zori
|
|
259
|
+
|
|
260
|
+
@zori.setter
|
|
261
|
+
def zori(self, val):
|
|
262
|
+
logger.warning("Changing zori is risky!")
|
|
263
|
+
self._zori = val
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def xinc(self):
|
|
267
|
+
"""The XINC (increment X) as property."""
|
|
268
|
+
return self._xinc
|
|
269
|
+
|
|
270
|
+
@xinc.setter
|
|
271
|
+
def xinc(self, val):
|
|
272
|
+
logger.warning("Changing xinc is risky!")
|
|
273
|
+
self._xinc = val
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def yinc(self):
|
|
277
|
+
"""The YINC (increment Y)."""
|
|
278
|
+
return self._yinc
|
|
279
|
+
|
|
280
|
+
@yinc.setter
|
|
281
|
+
def yinc(self, val):
|
|
282
|
+
logger.warning("Changing yinc is risky!")
|
|
283
|
+
self._yinc = val
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def zinc(self):
|
|
287
|
+
"""The ZINC (increment Z)."""
|
|
288
|
+
return self._zinc
|
|
289
|
+
|
|
290
|
+
@zinc.setter
|
|
291
|
+
def zinc(self, val):
|
|
292
|
+
logger.warning("Changing zinc is risky!")
|
|
293
|
+
self._zinc = val
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def rotation(self):
|
|
297
|
+
"""The rotation, anticlock from X axis in degrees."""
|
|
298
|
+
return self._rotation
|
|
299
|
+
|
|
300
|
+
@rotation.setter
|
|
301
|
+
def rotation(self, val):
|
|
302
|
+
logger.warning("Changing rotation is risky!")
|
|
303
|
+
self._rotation = val
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def ilines(self):
|
|
307
|
+
"""The inlines numbering vector."""
|
|
308
|
+
return self._ilines
|
|
309
|
+
|
|
310
|
+
@ilines.setter
|
|
311
|
+
def ilines(self, values):
|
|
312
|
+
self._ilines = values
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def xlines(self):
|
|
316
|
+
"""The xlines numbering vector."""
|
|
317
|
+
return self._xlines
|
|
318
|
+
|
|
319
|
+
@xlines.setter
|
|
320
|
+
def xlines(self, values):
|
|
321
|
+
self._xlines = values
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def zslices(self):
|
|
325
|
+
"""Return the time/depth slices as an int array (read only)."""
|
|
326
|
+
return np.array(range(self.nlay)) # This is a derived property
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def traceidcodes(self):
|
|
330
|
+
"""The trace identifaction codes array (ncol, nrow)."""
|
|
331
|
+
return self._traceidcodes
|
|
332
|
+
|
|
333
|
+
@traceidcodes.setter
|
|
334
|
+
def traceidcodes(self, values):
|
|
335
|
+
if isinstance(values, (int, str)):
|
|
336
|
+
self._traceidcodes = np.full((self.ncol, self.nrow), values, dtype=np.int32)
|
|
337
|
+
else:
|
|
338
|
+
if isinstance(values, list):
|
|
339
|
+
values = np.array(values, np.int32)
|
|
340
|
+
self._traceidcodes = values.reshape(self.ncol, self.nrow)
|
|
341
|
+
|
|
342
|
+
@property
|
|
343
|
+
def yflip(self):
|
|
344
|
+
"""The YFLIP indicator, 1 is normal, -1 means Y flipped.
|
|
345
|
+
|
|
346
|
+
YFLIP = 1 means a LEFT HANDED coordinate system with Z axis
|
|
347
|
+
positive down, while inline (col) follow East (X) and xline (rows)
|
|
348
|
+
follows North (Y), when rotation is zero.
|
|
349
|
+
"""
|
|
350
|
+
return self._yflip
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def zflip(self):
|
|
354
|
+
"""The ZFLIP indicator, 1 is normal, -1 means Z flipped.
|
|
355
|
+
|
|
356
|
+
ZFLIP = 1 and YFLIP = 1 means a LEFT HANDED coordinate system with Z axis
|
|
357
|
+
positive down, while inline (col) follow East (X) and xline (rows)
|
|
358
|
+
follows North (Y), when rotation is zero.
|
|
359
|
+
"""
|
|
360
|
+
return self._zflip
|
|
361
|
+
|
|
362
|
+
@property
|
|
363
|
+
def segyfile(self):
|
|
364
|
+
"""The input segy file name (str), if any (or None) (read-only)."""
|
|
365
|
+
return self._segyfile
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def filesrc(self):
|
|
369
|
+
"""The input file name (str), if any (or None) (read-only)."""
|
|
370
|
+
return self._filesrc
|
|
371
|
+
|
|
372
|
+
@filesrc.setter
|
|
373
|
+
def filesrc(self, name):
|
|
374
|
+
self._filesrc = name
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def values(self):
|
|
378
|
+
"""The values, as a 3D numpy (ncol, nrow, nlay), 4 byte float."""
|
|
379
|
+
return self._values
|
|
380
|
+
|
|
381
|
+
@values.setter
|
|
382
|
+
def values(self, values):
|
|
383
|
+
self._values = self._ensure_correct_values(values)
|
|
384
|
+
|
|
385
|
+
# =========================================================================
|
|
386
|
+
# Describe
|
|
387
|
+
# =========================================================================
|
|
388
|
+
|
|
389
|
+
def generate_hash(self, hashmethod="md5"):
|
|
390
|
+
"""Return a unique hash ID for current instance.
|
|
391
|
+
|
|
392
|
+
See :meth:`~xtgeo.common.sys.generic_hash()` for documentation.
|
|
393
|
+
|
|
394
|
+
.. versionadded:: 2.14
|
|
395
|
+
"""
|
|
396
|
+
required = (
|
|
397
|
+
"ncol",
|
|
398
|
+
"nrow",
|
|
399
|
+
"nlay",
|
|
400
|
+
"xori",
|
|
401
|
+
"yori",
|
|
402
|
+
"zori",
|
|
403
|
+
"xinc",
|
|
404
|
+
"yinc",
|
|
405
|
+
"zinc",
|
|
406
|
+
"yflip",
|
|
407
|
+
"zflip",
|
|
408
|
+
"rotation",
|
|
409
|
+
"values",
|
|
410
|
+
"ilines",
|
|
411
|
+
"xlines",
|
|
412
|
+
"traceidcodes",
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
gid = ""
|
|
416
|
+
for req in required:
|
|
417
|
+
gid += f"{getattr(self, '_' + req)}"
|
|
418
|
+
|
|
419
|
+
return generic_hash(gid, hashmethod=hashmethod)
|
|
420
|
+
|
|
421
|
+
def describe(self, flush=True):
|
|
422
|
+
"""Describe an instance by printing to stdout or return.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
flush (bool): If True, description is printed to stdout.
|
|
426
|
+
"""
|
|
427
|
+
dsc = XTGDescription()
|
|
428
|
+
dsc.title("Description of Cube instance")
|
|
429
|
+
dsc.txt("Object ID", id(self))
|
|
430
|
+
dsc.txt("File source", self._filesrc)
|
|
431
|
+
dsc.txt("Shape: NCOL, NROW, NLAY", self.ncol, self.nrow, self.nlay)
|
|
432
|
+
dsc.txt("Origins XORI, YORI, ZORI", self.xori, self.yori, self.zori)
|
|
433
|
+
dsc.txt("Increments XINC YINC ZINC", self.xinc, self.yinc, self.zinc)
|
|
434
|
+
dsc.txt("Rotation (anti-clock from X)", self.rotation)
|
|
435
|
+
dsc.txt("YFLIP flag", self.yflip)
|
|
436
|
+
np.set_printoptions(threshold=16)
|
|
437
|
+
dsc.txt("Inlines vector", self._ilines)
|
|
438
|
+
dsc.txt("Xlines vector", self._xlines)
|
|
439
|
+
dsc.txt("Time or depth slices vector", self.zslices)
|
|
440
|
+
dsc.txt("Values", self._values.reshape(-1), self._values.dtype)
|
|
441
|
+
np.set_printoptions(threshold=1000)
|
|
442
|
+
dsc.txt(
|
|
443
|
+
"Values, mean, stdev, minimum, maximum",
|
|
444
|
+
self.values.mean(),
|
|
445
|
+
self.values.std(),
|
|
446
|
+
self.values.min(),
|
|
447
|
+
self.values.max(),
|
|
448
|
+
)
|
|
449
|
+
dsc.txt("Trace ID codes", self._traceidcodes.reshape(-1))
|
|
450
|
+
msize = float(self.values.size * 4) / (1024 * 1024 * 1024)
|
|
451
|
+
dsc.txt("Minimum memory usage of array (GB)", msize)
|
|
452
|
+
|
|
453
|
+
if flush:
|
|
454
|
+
dsc.flush()
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
return dsc.astext()
|
|
458
|
+
|
|
459
|
+
# ==================================================================================
|
|
460
|
+
# Copy, swapping, cropping, thinning...
|
|
461
|
+
# ==================================================================================
|
|
462
|
+
|
|
463
|
+
def copy(self):
|
|
464
|
+
"""Deep copy of a Cube() object to another instance.
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
>>> mycube = xtgeo.cube_from_file(cube_dir + "/ib_test_cube2.segy")
|
|
468
|
+
>>> mycube2 = mycube.copy()
|
|
469
|
+
|
|
470
|
+
"""
|
|
471
|
+
xcube = Cube(
|
|
472
|
+
ncol=self.ncol,
|
|
473
|
+
nrow=self.nrow,
|
|
474
|
+
nlay=self.nlay,
|
|
475
|
+
xinc=self.xinc,
|
|
476
|
+
yinc=self.yinc,
|
|
477
|
+
zinc=self.zinc,
|
|
478
|
+
xori=self.xori,
|
|
479
|
+
yori=self.yori,
|
|
480
|
+
zori=self.zori,
|
|
481
|
+
yflip=self.yflip,
|
|
482
|
+
segyfile=self.segyfile,
|
|
483
|
+
rotation=self.rotation,
|
|
484
|
+
values=self.values.copy(),
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
xcube.filesrc = self._filesrc
|
|
488
|
+
|
|
489
|
+
xcube.ilines = self._ilines.copy()
|
|
490
|
+
xcube.xlines = self._xlines.copy()
|
|
491
|
+
xcube.traceidcodes = self._traceidcodes.copy()
|
|
492
|
+
xcube.metadata.required = xcube
|
|
493
|
+
|
|
494
|
+
return xcube
|
|
495
|
+
|
|
496
|
+
def swapaxes(self):
|
|
497
|
+
"""Swap the axes inline vs xline, keep origin."""
|
|
498
|
+
_cube_utils.swapaxes(self)
|
|
499
|
+
|
|
500
|
+
def resample(self, incube, sampling="nearest", outside_value=None):
|
|
501
|
+
"""Resample a Cube object into this instance.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
incube (Cube): A XTGeo Cube instance
|
|
505
|
+
sampling (str): Sampling algorithm: 'nearest' for nearest node
|
|
506
|
+
of 'trilinear' for trilinear interpoltion (more correct but
|
|
507
|
+
slower)
|
|
508
|
+
outside_value (None or float). If None, keep original, otherwise
|
|
509
|
+
use this value
|
|
510
|
+
|
|
511
|
+
Raises:
|
|
512
|
+
ValueError: If cubes do not overlap
|
|
513
|
+
|
|
514
|
+
Example:
|
|
515
|
+
|
|
516
|
+
>>> import xtgeo
|
|
517
|
+
>>> mycube1 = xtgeo.cube_from_file(cube_dir + "/ib_test_cube2.segy")
|
|
518
|
+
>>> mycube2 = xtgeo.Cube(
|
|
519
|
+
... xori=777574,
|
|
520
|
+
... yori=6736507,
|
|
521
|
+
... zori=1000,
|
|
522
|
+
... xinc=10,
|
|
523
|
+
... yinc=10,
|
|
524
|
+
... zinc=4,
|
|
525
|
+
... ncol=100,
|
|
526
|
+
... nrow=100,
|
|
527
|
+
... nlay=100,
|
|
528
|
+
... yflip=mycube1.yflip,
|
|
529
|
+
... rotation=mycube1.rotation
|
|
530
|
+
... )
|
|
531
|
+
>>> mycube2.resample(mycube1)
|
|
532
|
+
|
|
533
|
+
"""
|
|
534
|
+
_cube_utils.resample(
|
|
535
|
+
self, incube, sampling=sampling, outside_value=outside_value
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
def do_thinning(self, icol, jrow, klay):
|
|
539
|
+
"""Thinning the cube by removing every N column, row and/or layer.
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
icol (int): Thinning factor for columns (usually inlines)
|
|
543
|
+
jrow (int): Thinning factor for rows (usually xlines)
|
|
544
|
+
klay (int): Thinning factor for layers
|
|
545
|
+
|
|
546
|
+
Raises:
|
|
547
|
+
ValueError: If icol, jrow or klay are out of reasonable range
|
|
548
|
+
|
|
549
|
+
Example:
|
|
550
|
+
|
|
551
|
+
>>> mycube1 = Cube(cube_dir + "/ib_test_cube2.segy")
|
|
552
|
+
>>> mycube1.do_thinning(2, 2, 1) # keep every second column, row
|
|
553
|
+
>>> mycube1.to_file(outdir + '/mysegy_smaller.segy')
|
|
554
|
+
|
|
555
|
+
"""
|
|
556
|
+
_cube_utils.thinning(self, icol, jrow, klay)
|
|
557
|
+
|
|
558
|
+
def do_cropping(self, icols, jrows, klays, mode="edges"):
|
|
559
|
+
"""Cropping the cube by removing rows, columns, layers.
|
|
560
|
+
|
|
561
|
+
Note that input boundary checking is currently lacking, and this
|
|
562
|
+
is a currently a user responsibility!
|
|
563
|
+
|
|
564
|
+
The 'mode' is used to determine to different 'approaches' on
|
|
565
|
+
cropping. Examples for icols and mode 'edges':
|
|
566
|
+
Here the tuple (N, M) will cut N first rows and M last rows.
|
|
567
|
+
|
|
568
|
+
However, if mode is 'inclusive' then, it defines the range
|
|
569
|
+
of rows to be included, and the numbering now shall be the
|
|
570
|
+
INLINE, XLINE and DEPTH/TIME mode.
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
icols (int tuple): Cropping front, end of rows, or inclusive range
|
|
574
|
+
jrows (int tuple): Cropping front, end of columns, or
|
|
575
|
+
inclusive range
|
|
576
|
+
klays (int tuple ): Cropping top, base layers, or inclusive range.
|
|
577
|
+
mode (str): 'Default is 'edges'; alternative is 'inclusive'
|
|
578
|
+
|
|
579
|
+
Example:
|
|
580
|
+
Crop 10 columns from front, 2 from back, then 20 rows in front,
|
|
581
|
+
40 in back, then no cropping of layers::
|
|
582
|
+
|
|
583
|
+
>>> import xtgeo
|
|
584
|
+
>>> mycube1 = xtgeo.cube_from_file(cube_dir + "/ib_test_cube2.segy")
|
|
585
|
+
>>> mycube2 = mycube1.copy()
|
|
586
|
+
>>> mycube1.do_cropping((10, 2), (20, 40), (0, 0))
|
|
587
|
+
>>> mycube1.to_file(outdir + '/mysegy_smaller.segy')
|
|
588
|
+
|
|
589
|
+
In stead, do cropping as 'inclusive' where inlines, xlines, slices
|
|
590
|
+
arrays are known::
|
|
591
|
+
|
|
592
|
+
>>> mycube2.do_cropping((11, 32), (112, 114), (150, 200))
|
|
593
|
+
|
|
594
|
+
"""
|
|
595
|
+
useicols = icols
|
|
596
|
+
usejrows = jrows
|
|
597
|
+
useklays = klays
|
|
598
|
+
|
|
599
|
+
if mode == "inclusive":
|
|
600
|
+
# transfer to 'numbers to row/col/lay to remove' in front end ...
|
|
601
|
+
useicols = (
|
|
602
|
+
icols[0] - self._ilines[0],
|
|
603
|
+
self._ilines[self._ncol - 1] - icols[1],
|
|
604
|
+
)
|
|
605
|
+
usejrows = (
|
|
606
|
+
jrows[0] - self._xlines[0],
|
|
607
|
+
self._xlines[self._nrow - 1] - jrows[1],
|
|
608
|
+
)
|
|
609
|
+
ntop = int((klays[0] - self.zori) / self.zinc)
|
|
610
|
+
nbot = int((self.zori + self.nlay * self.zinc - klays[1] - 1) / (self.zinc))
|
|
611
|
+
useklays = (ntop, nbot)
|
|
612
|
+
|
|
613
|
+
logger.info(
|
|
614
|
+
"Cropping at all cube sides: %s %s %s", useicols, usejrows, useklays
|
|
615
|
+
)
|
|
616
|
+
_cube_utils.cropping(self, useicols, usejrows, useklays)
|
|
617
|
+
|
|
618
|
+
def values_dead_traces(self, newvalue):
|
|
619
|
+
"""Set values for traces flagged as dead.
|
|
620
|
+
|
|
621
|
+
Dead traces have traceidcodes 2 and corresponding values in the cube
|
|
622
|
+
will here receive a constant value to mimic "undefined".
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
newvalue (float): Set cube values to newvalues where traceid is 2.
|
|
626
|
+
|
|
627
|
+
Return:
|
|
628
|
+
oldvalue (float): The estimated simple 'average' of old value will
|
|
629
|
+
be returned as (max + min)/2. If no dead traces, return None.
|
|
630
|
+
"""
|
|
631
|
+
logger.info("Set values for dead traces, if any")
|
|
632
|
+
|
|
633
|
+
if 2 in self._traceidcodes:
|
|
634
|
+
minval = self._values[self._traceidcodes == 2].min()
|
|
635
|
+
maxval = self._values[self._traceidcodes == 2].max()
|
|
636
|
+
# a bit weird calculation of mean but kept for backward compatibility
|
|
637
|
+
self._values[self._traceidcodes == 2] = newvalue
|
|
638
|
+
return 0.5 * (minval + maxval)
|
|
639
|
+
|
|
640
|
+
return None
|
|
641
|
+
|
|
642
|
+
def get_xy_value_from_ij(self, iloc, jloc, ixline=False, zerobased=False):
|
|
643
|
+
"""Returns x, y coordinate from a single i j location.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
iloc (int): I (col) location (base is 1)
|
|
647
|
+
jloc (int): J (row) location (base is 1)
|
|
648
|
+
ixline (bool): If True, then input locations are inline and xline position
|
|
649
|
+
zerobased (bool): If True, first index is 0, else it is 1. This does not
|
|
650
|
+
apply when ixline is set to True.
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
The X, Y coordinate pair.
|
|
654
|
+
"""
|
|
655
|
+
xval, yval = _cube_utils.get_xy_value_from_ij(
|
|
656
|
+
self, iloc, jloc, ixline=ixline, zerobased=zerobased
|
|
657
|
+
)
|
|
658
|
+
return xval, yval
|
|
659
|
+
|
|
660
|
+
def compute_attributes_in_window(
|
|
661
|
+
self,
|
|
662
|
+
upper: RegularSurface | float,
|
|
663
|
+
lower: RegularSurface | float,
|
|
664
|
+
ndiv: int = 10,
|
|
665
|
+
interpolation: Literal["cubic", "linear"] = "cubic",
|
|
666
|
+
minimum_thickness: float = 0.0,
|
|
667
|
+
) -> dict[RegularSurface]:
|
|
668
|
+
"""Return a cube's attributes as a set of surfaces, given two input surfaces.
|
|
669
|
+
|
|
670
|
+
The attributes are computed vertically (per column) within a window defined by
|
|
671
|
+
the two input surfaces and/or levels.
|
|
672
|
+
|
|
673
|
+
The statistical measures can be min, max, mean, variance etc. A complete list of
|
|
674
|
+
supported attributes is given below.
|
|
675
|
+
|
|
676
|
+
* 'max' for maximum
|
|
677
|
+
|
|
678
|
+
* 'min' for minimum
|
|
679
|
+
|
|
680
|
+
* 'rms' for root mean square
|
|
681
|
+
|
|
682
|
+
* 'mean' for expected value
|
|
683
|
+
|
|
684
|
+
* 'var' for variance (population var; https://en.wikipedia.org/wiki/Variance)
|
|
685
|
+
|
|
686
|
+
* 'maxpos' for maximum of positive values
|
|
687
|
+
|
|
688
|
+
* 'maxneg' for negative maximum of negative values
|
|
689
|
+
|
|
690
|
+
* 'maxabs' for maximum of absolute values
|
|
691
|
+
|
|
692
|
+
* 'sumpos' for sum of positive values using cube sampling resolution
|
|
693
|
+
|
|
694
|
+
* 'sumneg' for sum of negative values using cube sampling resolution
|
|
695
|
+
|
|
696
|
+
* 'meanabs' for mean of absolute values
|
|
697
|
+
|
|
698
|
+
* 'meanpos' for mean of positive values
|
|
699
|
+
|
|
700
|
+
* 'meanneg' for mean of negative values
|
|
701
|
+
|
|
702
|
+
* 'upper' will return a copy of the upper surface applied
|
|
703
|
+
|
|
704
|
+
* 'lower' will return a copy of the lower surface applied
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
upper: The uppermost surface or constant level to compute within.
|
|
709
|
+
lower: The lower surface or level to compute within.
|
|
710
|
+
ndiv: Number of intervals for sampling within zrange. Default is 10.
|
|
711
|
+
using 0.1 of cube Z increment as basis. A higher ndiv will increase
|
|
712
|
+
CPU time and memory usage, but also increase the precision of the
|
|
713
|
+
result.
|
|
714
|
+
interpolation: 'cubic' or 'linear' for interpolation of the
|
|
715
|
+
seismic signal, default here is 'cubic'.
|
|
716
|
+
minimum_thickness: Minimum thickness (isochore or isochron) between the
|
|
717
|
+
two surfaces. If the thickness is less or equal than this value,
|
|
718
|
+
the result will be masked. Default is 0.0.
|
|
719
|
+
|
|
720
|
+
Example::
|
|
721
|
+
|
|
722
|
+
>>> import xtgeo
|
|
723
|
+
>>> cube = xtgeo.cube_from_file("mycube.segy")
|
|
724
|
+
>>> surf = xtgeo.surface_from_file("topreek.gri")
|
|
725
|
+
>>> # sample in a total range of 30 m, 15 units above and 15 units below:
|
|
726
|
+
>>> attrs = cube.compute_attributes_in_window((surf-15), (surf + 15))
|
|
727
|
+
>>> attrs["max"].to_file("max.gri") # save the 'max' attribute to file
|
|
728
|
+
|
|
729
|
+
Note:
|
|
730
|
+
This method is a significantly improved version of the
|
|
731
|
+
:meth:`slice_cube_window` method within `RegularSurface()`, and it is
|
|
732
|
+
strongly recommended to replace the former with this as soon as possible.
|
|
733
|
+
|
|
734
|
+
.. versionadded:: 4.1
|
|
735
|
+
|
|
736
|
+
"""
|
|
737
|
+
return _cube_window_attributes.CubeAttrs(
|
|
738
|
+
self, upper, lower, ndiv, interpolation, minimum_thickness
|
|
739
|
+
).result()
|
|
740
|
+
|
|
741
|
+
# =========================================================================
|
|
742
|
+
# Cube extractions, e.g. XSection
|
|
743
|
+
# =========================================================================
|
|
744
|
+
|
|
745
|
+
def get_randomline(
|
|
746
|
+
self,
|
|
747
|
+
fencespec,
|
|
748
|
+
zmin=None,
|
|
749
|
+
zmax=None,
|
|
750
|
+
zincrement=None,
|
|
751
|
+
hincrement=None,
|
|
752
|
+
atleast=5,
|
|
753
|
+
nextend=2,
|
|
754
|
+
sampling="nearest",
|
|
755
|
+
):
|
|
756
|
+
"""Get a randomline from a fence spesification.
|
|
757
|
+
|
|
758
|
+
This randomline will be a 2D numpy with depth/time on the vertical
|
|
759
|
+
axis, and length along as horizontal axis. Undefined values will have
|
|
760
|
+
the np.nan value.
|
|
761
|
+
|
|
762
|
+
The input fencespec is either a 2D numpy where each row is X, Y, Z, HLEN,
|
|
763
|
+
where X, Y are UTM coordinates, Z is depth/time, and HLEN is a
|
|
764
|
+
length along the fence, or a Polygons instance.
|
|
765
|
+
|
|
766
|
+
If input fencspec is a numpy 2D, it is important that the HLEN array
|
|
767
|
+
has a constant increment and ideally a sampling that is less than the
|
|
768
|
+
Cube resolution. If a Polygons() instance, this is automated!
|
|
769
|
+
|
|
770
|
+
Args:
|
|
771
|
+
fencespec (:obj:`~numpy.ndarray` or :class:`~xtgeo.xyz.polygons.Polygons`):
|
|
772
|
+
2D numpy with X, Y, Z, HLEN as rows or a xtgeo Polygons() object.
|
|
773
|
+
zmin (float): Minimum Z (default is Cube Z minima/origin)
|
|
774
|
+
zmax (float): Maximum Z (default is Cube Z maximum)
|
|
775
|
+
zincrement (float): Sampling vertically, default is Cube ZINC/2
|
|
776
|
+
hincrement (float or bool): Resampling horizontally. This applies only
|
|
777
|
+
if the fencespec is a Polygons() instance. If None (default),
|
|
778
|
+
the distance will be deduced automatically.
|
|
779
|
+
atleast (int): Minimum number of horizontal samples (only if
|
|
780
|
+
fencespec is a Polygons instance)
|
|
781
|
+
nextend (int): Extend with nextend * hincrement in both ends (only if
|
|
782
|
+
fencespec is a Polygons instance)
|
|
783
|
+
sampling (str): Algorithm, 'nearest' or 'trilinear' (first is
|
|
784
|
+
faster, second is more precise for continuous fields)
|
|
785
|
+
|
|
786
|
+
Returns:
|
|
787
|
+
A tuple: (hmin, hmax, vmin, vmax, ndarray2d)
|
|
788
|
+
|
|
789
|
+
Raises:
|
|
790
|
+
ValueError: Input fence is not according to spec.
|
|
791
|
+
|
|
792
|
+
.. versionchanged:: 2.1 support for Polygons() as fencespec, and keywords
|
|
793
|
+
hincrement, atleast and sampling
|
|
794
|
+
|
|
795
|
+
.. seealso::
|
|
796
|
+
Class :class:`~xtgeo.xyz.polygons.Polygons`
|
|
797
|
+
The method :meth:`~xtgeo.xyz.polygons.Polygons.get_fence()` which can be
|
|
798
|
+
used to pregenerate `fencespec`
|
|
799
|
+
|
|
800
|
+
"""
|
|
801
|
+
if not isinstance(fencespec, (np.ndarray, Polygons)):
|
|
802
|
+
raise ValueError(
|
|
803
|
+
"fencespec must be a numpy or a Polygons() object. "
|
|
804
|
+
f"Current type is {type(fencespec)}"
|
|
805
|
+
)
|
|
806
|
+
logger.info("Getting randomline...")
|
|
807
|
+
res = _cube_utils.get_randomline(
|
|
808
|
+
self,
|
|
809
|
+
fencespec,
|
|
810
|
+
zmin=zmin,
|
|
811
|
+
zmax=zmax,
|
|
812
|
+
zincrement=zincrement,
|
|
813
|
+
hincrement=hincrement,
|
|
814
|
+
atleast=atleast,
|
|
815
|
+
nextend=nextend,
|
|
816
|
+
sampling=sampling,
|
|
817
|
+
)
|
|
818
|
+
logger.info("Getting randomline... DONE")
|
|
819
|
+
return res
|
|
820
|
+
|
|
821
|
+
# =========================================================================
|
|
822
|
+
# Import and export
|
|
823
|
+
# =========================================================================
|
|
824
|
+
|
|
825
|
+
@classmethod
|
|
826
|
+
def _read_file(cls, sfile, fformat="guess"):
|
|
827
|
+
"""Import cube data from file.
|
|
828
|
+
|
|
829
|
+
If fformat is not provided, the file type will be guessed based
|
|
830
|
+
on file extension (e.g. segy og sgy for SEGY format)
|
|
831
|
+
|
|
832
|
+
Args:
|
|
833
|
+
sfile (str): Filename (as string or pathlib.Path instance).
|
|
834
|
+
fformat (str): file format guess/segy/rms_regular/xtgregcube
|
|
835
|
+
where 'guess' is default. Regard 'xtgrecube' format as experimental.
|
|
836
|
+
deadtraces (float): Set 'dead' trace values to this value (SEGY
|
|
837
|
+
only). Default is UNDEF value (a very large number).
|
|
838
|
+
|
|
839
|
+
Raises:
|
|
840
|
+
OSError: if the file cannot be read (e.g. not found)
|
|
841
|
+
ValueError: Input is invalid
|
|
842
|
+
|
|
843
|
+
Example::
|
|
844
|
+
|
|
845
|
+
>>> zz = Cube()
|
|
846
|
+
>>> zz.from_file(cube_dir + "/ib_test_cube2.segy")
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
"""
|
|
850
|
+
mfile = FileWrapper(sfile)
|
|
851
|
+
fmt = mfile.fileformat(fformat)
|
|
852
|
+
kwargs = _data_reader_factory(fmt)(mfile)
|
|
853
|
+
kwargs["filesrc"] = mfile.file
|
|
854
|
+
return cls(**kwargs)
|
|
855
|
+
|
|
856
|
+
def to_file(self, sfile, fformat="segy", pristine=False, engine=None):
|
|
857
|
+
"""Export cube data to file.
|
|
858
|
+
|
|
859
|
+
Args:
|
|
860
|
+
sfile (str): Filename
|
|
861
|
+
fformat (str, optional): file format 'segy' (default) or
|
|
862
|
+
'rms_regular'
|
|
863
|
+
pristine (bool): If True, make SEGY from scratch.
|
|
864
|
+
engine (str): Which "engine" to use.
|
|
865
|
+
|
|
866
|
+
Example::
|
|
867
|
+
>>> import xtgeo
|
|
868
|
+
>>> zz = xtgeo.cube_from_file(cube_dir + "/ib_test_cube2.segy")
|
|
869
|
+
>>> zz.to_file(outdir + '/some.rmsreg')
|
|
870
|
+
"""
|
|
871
|
+
fobj = FileWrapper(sfile, mode="wb")
|
|
872
|
+
|
|
873
|
+
fobj.check_folder(raiseerror=OSError)
|
|
874
|
+
|
|
875
|
+
if engine is not None:
|
|
876
|
+
warnings.warn(
|
|
877
|
+
"Providing an 'engine' value is no longer supported and will have no "
|
|
878
|
+
"effect in the future. segyio will eventually be used by default. "
|
|
879
|
+
"Current default engine is 'xtgeo'.",
|
|
880
|
+
UserWarning,
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
if fformat in FileFormat.SEGY.value:
|
|
884
|
+
_cube_export.export_segy(self, fobj.name, pristine=pristine)
|
|
885
|
+
elif fformat == "rms_regular":
|
|
886
|
+
_cube_export.export_rmsreg(self, fobj.name)
|
|
887
|
+
elif fformat == "xtgregcube":
|
|
888
|
+
_cube_export.export_xtgregcube(self, fobj.name)
|
|
889
|
+
else:
|
|
890
|
+
extensions = FileFormat.extensions_string([FileFormat.SEGY])
|
|
891
|
+
raise InvalidFileFormatError(
|
|
892
|
+
f"File format {fformat} is invalid for type Cube. "
|
|
893
|
+
f"Supported formats are {extensions}."
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
def to_roxar(
|
|
897
|
+
self,
|
|
898
|
+
project: Any,
|
|
899
|
+
name: str,
|
|
900
|
+
folder: str | None = None,
|
|
901
|
+
propname: str = "seismic_attribute",
|
|
902
|
+
domain: str = "time",
|
|
903
|
+
compression: tuple[str, float] = ("wavelet", 5.0),
|
|
904
|
+
target: str = "seismic",
|
|
905
|
+
): # pragma: no cover
|
|
906
|
+
"""Export (transfer) a cube from a XTGeo cube object to Roxar data.
|
|
907
|
+
|
|
908
|
+
Note:
|
|
909
|
+
When project is file path (direct access, outside RMS) then
|
|
910
|
+
``to_roxar()`` will implicitly do a project save. Otherwise, the project
|
|
911
|
+
will not be saved until the user do an explicit project save action.
|
|
912
|
+
|
|
913
|
+
Args:
|
|
914
|
+
project: Inside RMS use the magic 'project',
|
|
915
|
+
else use path to RMS project, or a project reference
|
|
916
|
+
name: Name of cube (seismic data) within RMS project.
|
|
917
|
+
folder: Cubes may be stored under a folder in the tree, use '/'
|
|
918
|
+
to seperate subfolders.
|
|
919
|
+
propname: Name of grid property; only relevant when target is "grid" and
|
|
920
|
+
defaults to "seismic_attribute"
|
|
921
|
+
domain: 'time' (default) or 'depth'
|
|
922
|
+
compression: Reference to Roxar API 'compression method' and 'compression
|
|
923
|
+
tolerance', but implementation is pending. Hence inactive.
|
|
924
|
+
target: Optionally, the seismic cube can be written to the `Grid model`
|
|
925
|
+
tree in RMS. Internally, it will be convert to a "box" grid with one
|
|
926
|
+
gridproperty, before it is written to RMS. The ``compression``and
|
|
927
|
+
``domain`` are not relevant when writing to grid model.
|
|
928
|
+
|
|
929
|
+
Raises:
|
|
930
|
+
To be described...
|
|
931
|
+
|
|
932
|
+
Example::
|
|
933
|
+
|
|
934
|
+
zz = xtgeo.cube_from_file('myfile.segy')
|
|
935
|
+
zz.to_roxar(project, 'reek_cube')
|
|
936
|
+
# write cube to "Grid model" tree in RMS instead
|
|
937
|
+
zz.to_roxar(project, 'cube_as_grid', propname="impedance", target="grid")
|
|
938
|
+
|
|
939
|
+
.. versionchanged:: 3.4 Add ``target`` and ``propname`` keys
|
|
940
|
+
"""
|
|
941
|
+
|
|
942
|
+
if "grid" in target.lower():
|
|
943
|
+
_tmpgrd = grid_from_cube(self, propname=name)
|
|
944
|
+
_tmpprop = _tmpgrd.props[0]
|
|
945
|
+
_tmpprop.name = propname if propname else "seismic_attribute"
|
|
946
|
+
_tmpgrd.to_roxar(project, name)
|
|
947
|
+
_tmpprop.to_roxar(project, name, _tmpprop.name)
|
|
948
|
+
|
|
949
|
+
else:
|
|
950
|
+
_cube_roxapi.export_cube_roxapi(
|
|
951
|
+
self,
|
|
952
|
+
project,
|
|
953
|
+
name,
|
|
954
|
+
folder=folder,
|
|
955
|
+
domain=domain,
|
|
956
|
+
compression=compression,
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
def _ensure_correct_values(
|
|
960
|
+
self,
|
|
961
|
+
values: None | bool | float | list | tuple | np.ndarray | np.ma.MaskedArray,
|
|
962
|
+
) -> np.ndarray:
|
|
963
|
+
"""Ensures that values is a 3D numpy (ncol, nrow, nlay), C order.
|
|
964
|
+
|
|
965
|
+
Args:
|
|
966
|
+
values: Values to process.
|
|
967
|
+
|
|
968
|
+
"""
|
|
969
|
+
return_array = None
|
|
970
|
+
if values is None or isinstance(values, bool):
|
|
971
|
+
return_array = self._ensure_correct_values(0.0)
|
|
972
|
+
|
|
973
|
+
elif isinstance(values, numbers.Number):
|
|
974
|
+
array = np.zeros(self.dimensions, dtype=np.float32) + values
|
|
975
|
+
return_array = array.astype(np.float32) # ensure 32 bit floats
|
|
976
|
+
|
|
977
|
+
elif isinstance(values, np.ndarray):
|
|
978
|
+
# if the input is a maskedarray; need to convert and fill with zero
|
|
979
|
+
if isinstance(values, np.ma.MaskedArray):
|
|
980
|
+
warnings.warn(
|
|
981
|
+
"Input values is a masked numpy array, and masked nodes "
|
|
982
|
+
"will be set to zero in the cube instance.",
|
|
983
|
+
UserWarning,
|
|
984
|
+
)
|
|
985
|
+
values = np.ma.filled(values, fill_value=0)
|
|
986
|
+
|
|
987
|
+
exp_len = np.prod(self.dimensions)
|
|
988
|
+
if (
|
|
989
|
+
values.size != exp_len
|
|
990
|
+
or values.ndim not in (1, 3)
|
|
991
|
+
or values.shape != self.dimensions
|
|
992
|
+
):
|
|
993
|
+
raise ValueError(
|
|
994
|
+
"Input is of wrong shape or dimensions: "
|
|
995
|
+
f"{values.shape}, expected {self.dimensions}"
|
|
996
|
+
"or ({exp_len},)"
|
|
997
|
+
)
|
|
998
|
+
|
|
999
|
+
values = values.reshape(self.dimensions).astype(np.float32)
|
|
1000
|
+
|
|
1001
|
+
if not values.flags.c_contiguous:
|
|
1002
|
+
values = np.ascontiguousarray(values)
|
|
1003
|
+
return_array = values
|
|
1004
|
+
|
|
1005
|
+
elif isinstance(values, (list, tuple)):
|
|
1006
|
+
exp_len = int(np.prod(self.dimensions))
|
|
1007
|
+
if len(values) != exp_len:
|
|
1008
|
+
raise ValueError(
|
|
1009
|
+
"The length of the input list or tuple is incorrect"
|
|
1010
|
+
f"Input length is {len(values)} while expected length is {exp_len}"
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
return_array = np.array(values, dtype=np.float32).reshape(self.dimensions)
|
|
1014
|
+
|
|
1015
|
+
else:
|
|
1016
|
+
raise ValueError(
|
|
1017
|
+
f"Cannot process _ensure_correct_values with input values: {values}"
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
if return_array is not None:
|
|
1021
|
+
return return_array
|
|
1022
|
+
|
|
1023
|
+
raise RuntimeError("Unexpected error, return values are None")
|