Skip to content

Commit 6337d97

Browse files
authored
Merge pull request #2 from jcrivenaes/surf-slice-cube-window
Surf slice cube window
2 parents 693cecc + ee2cc84 commit 6337d97

File tree

7 files changed

+227
-15
lines changed

7 files changed

+227
-15
lines changed

src/xtgeo/cube/_cube_roxapi.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ def _roxapi_cube_to_xtgeo(self, rcube):
6161
self._xlines = np.array(range(xlstart, self._nrow + xlstart, xlincr),
6262
dtype=np.int32)
6363

64+
# roxar API does not store traceid codes, assume 1
65+
self._traceidcodes = np.ones((self._ncol, self._nrow), dtype=np.int32)
66+
6467
if rcube.is_empty:
6568
xtg.warn('Cube has no data; assume 0')
6669
else:

src/xtgeo/grid3d/_grid_etc1.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,58 @@ def collapse_inactive_cells(self):
440440
return self
441441

442442

443+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
444+
# Do cropping
445+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
446+
def do_cropping(self, spec):
447+
"""Do cropping of geometry (and params)."""
448+
449+
(ic1, ic2), (jc1, jc2), (kc1, kc2) = spec
450+
451+
if ic1 < 1 or ic2 > self.ncol or jc1 < 1 or jc2 > self.nrow or \
452+
kc1 < 1 or kc2 > self.nlay:
453+
454+
raise ValueError('Boundary for tuples not matching grid'
455+
'NCOL, NROW, NLAY')
456+
457+
# compute size of new cropped grid
458+
nncol = ic2 - ic1 + 1
459+
nnrow = jc2 - jc1 + 1
460+
nnlay = kc2 - kc1 + 1
461+
462+
ntot = nncol * nnrow * nnlay
463+
ncoord = (nncol + 1) * (nnrow + 1) * 2 * 3
464+
nzcorn = nncol * nnrow * (nnlay + 1) * 4
465+
466+
new_num_act = _cxtgeo.new_intpointer()
467+
new_p_coord_v = _cxtgeo.new_doublearray(ncoord)
468+
new_p_zcorn_v = _cxtgeo.new_doublearray(nzcorn)
469+
new_p_actnum_v = _cxtgeo.new_intarray(ntot)
470+
471+
_cxtgeo.grd3d_crop_geometry(self.ncol, self.nrow, self.nlay,
472+
self._p_coord_v,
473+
self._p_zcorn_v,
474+
self._p_actnum_v,
475+
new_p_coord_v,
476+
new_p_zcorn_v,
477+
new_p_actnum_v,
478+
ic1, ic2, jc1, jc2, kc1, kc2,
479+
new_num_act,
480+
0,
481+
xtg_verbose_level)
482+
483+
self._p_coord_v = new_p_coord_v
484+
self._p_zcorn_v = new_p_zcorn_v
485+
self._p_actnum_v = new_p_actnum_v
486+
487+
self._nactive = _cxtgeo.intpointer_value(new_num_act)
488+
self._ncol = nncol
489+
self._nrow = nnrow
490+
self._nlay = nnlay
491+
492+
# TODO: subgrid
493+
494+
443495
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
444496
# Reduce grid to one layer
445497
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/xtgeo/grid3d/grid.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,25 @@ def collapse_inactive_cells(self):
715715

716716
self = _grid_etc1.collapse_inactive_cells(self)
717717

718+
def do_cropping(self, croprange):
719+
"""Reduce the grid size by cropping, the grid will have new dimensions.
720+
721+
Args:
722+
croprange (tuple): Crop range on the form
723+
((i1, i2), (j1, j2), (k1, k2)), from i1, i2 means from i1 to
724+
i2, etc (inclusive).
725+
726+
Example::
727+
728+
>>> from xtgeo.grid3d import Grid
729+
>>> gf = Grid('gullfaks2.roff')
730+
>>> gf.do_cropping((3, 6), (4, 20), (1, 10))
731+
>>> gf.to_file('gf_reduced.roff')
732+
733+
"""
734+
735+
_grid_etc1.do_cropping(self, croprange)
736+
718737
def reduce_to_one_layer(self):
719738
"""Reduce the grid to one single single layer.
720739

src/xtgeo/surface/_regsurf_cube.py

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def slice_cube_window(self, cube, zsurf=None, other=None,
9595
zrange=10, ndiv=None, attribute='max',
9696
maskthreshold=0.1, snapxy=False,
9797
showprogress=False, deadtraces=True,
98-
deletecube=False):
98+
deletecube=False, algorithm=1):
9999

100100
"""Slice Cube with a window and extract attribute(s)
101101
@@ -140,12 +140,18 @@ def slice_cube_window(self, cube, zsurf=None, other=None,
140140
# This will run slice in a loop within a window. Then, numpy methods
141141
# are applied to get the attributes
142142

143-
if other is None:
143+
if other is None and algorithm == 1:
144144
attvalues = _slice_constant_window(this, cube, sampling, zrange,
145145
ndiv, mask, attrlist, snapxy,
146146
showprogress=showprogress,
147147
deadtraces=deadtraces,
148148
deletecube=deletecube)
149+
elif other is None and algorithm == 2:
150+
attvalues = _slice_constant_window2(this, cube, sampling, zrange,
151+
ndiv, mask, attrlist, snapxy,
152+
showprogress=showprogress,
153+
deadtraces=deadtraces,
154+
deletecube=deletecube)
149155
else:
150156
attvalues = _slice_between_surfaces(this, cube, sampling, other,
151157
other_position, zrange,
@@ -157,19 +163,20 @@ def slice_cube_window(self, cube, zsurf=None, other=None,
157163

158164
results = dict()
159165

160-
for attr in attrlist:
161-
scopy = self.copy()
162-
scopy.values = attvalues[attr]
163-
results[attr] = scopy
164-
165-
# for backward compatibility
166-
if qattr_is_string:
167-
self.values = attvalues[attrlist[0]]
168-
return None
166+
if algorithm != 2:
167+
for attr in attrlist:
168+
scopy = self.copy()
169+
scopy.values = attvalues[attr]
170+
results[attr] = scopy
169171

170-
return results
172+
# for backward compatibility
173+
if qattr_is_string:
174+
self.values = attvalues[attrlist[0]]
175+
return None
171176

172-
logger.info('Mean of cube attribute is {}'.format(self.values.mean()))
177+
return results
178+
else:
179+
return None
173180

174181

175182
def _slice_constant_window(this, cube, sampling, zrange,
@@ -224,6 +231,77 @@ def _slice_constant_window(this, cube, sampling, zrange,
224231
return attvalues # this is dict with numpies, one per attribute
225232

226233

234+
def _slice_constant_window2(this, cube, sampling, zrange,
235+
ndiv, mask, attrlist, snapxy, showprogress=False,
236+
deadtraces=True, deletecube=False):
237+
"""Slice a window, (constant in vertical extent); faster and better
238+
algorithm (algorithm2).
239+
"""
240+
241+
zincr = zrange / float(ndiv)
242+
ztmp = this.copy()
243+
ztmp.values = ztmp.values - zincr * (ndiv + 1)
244+
245+
if mask:
246+
opt2 = 0
247+
else:
248+
opt2 = 1
249+
250+
if deadtraces:
251+
# set dead traces to cxtgeo UNDEF -> special treatment in the C code
252+
olddead = cube.values_dead_traces(_cxtgeo.UNDEF)
253+
254+
cubeval1d = np.ravel(cube.values, order='C')
255+
256+
usesampling = 0
257+
if sampling == 'trilinear':
258+
usesampling = 1
259+
if snapxy:
260+
usesampling = 2
261+
262+
# allocate the attribute maps
263+
nattr = 5 # predefined attributes
264+
nsurf = ztmp.ncol * ztmp.nrow * nattr
265+
266+
istat, attrmaps = _cxtgeo.surf_slice_cube_window(
267+
cube.ncol,
268+
cube.nrow,
269+
cube.nlay,
270+
cube.xori,
271+
cube.xinc,
272+
cube.yori,
273+
cube.yinc,
274+
cube.zori,
275+
cube.zinc,
276+
cube.rotation,
277+
cube.yflip,
278+
cubeval1d,
279+
ztmp.ncol,
280+
ztmp.nrow,
281+
ztmp.xori,
282+
ztmp.xinc,
283+
ztmp.yori,
284+
ztmp.yinc,
285+
ztmp.yflip,
286+
ztmp.rotation,
287+
ztmp.get_values1d(),
288+
zincr,
289+
ndiv * 2,
290+
nsurf,
291+
nattr,
292+
usesampling,
293+
opt2,
294+
xtg_verbose_level)
295+
296+
if istat != 0:
297+
logger.warning('Problem, ISTAT = {}'.format(istat))
298+
299+
if deadtraces:
300+
cube.values_dead_traces(olddead) # reset value for dead traces
301+
302+
return istat
303+
304+
227305
def _slice_between_surfaces(this, cube, sampling, other, other_position,
228306
zrange, ndiv, mask, attrlist, mthreshold,
229307
snapxy, showprogress=False, deadtraces=True,

src/xtgeo/surface/regular_surface.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,7 +1325,7 @@ def slice_cube_window(self, cube, zsurf=None, other=None,
13251325
other_position='below', sampling='nearest',
13261326
mask=True, zrange=None, ndiv=None, attribute='max',
13271327
maskthreshold=0.1, snapxy=False, showprogress=False,
1328-
deadtraces=True):
1328+
deadtraces=True, algorithm=1):
13291329
"""Slice the cube within a vertical window and get the statistical
13301330
attrubute.
13311331
@@ -1374,6 +1374,7 @@ def slice_cube_window(self, cube, zsurf=None, other=None,
13741374
deadtraces (bool): If True (default) then dead cube traces
13751375
(given as value 2 in SEGY trace headers), are treated as
13761376
undefined, nad map will be undefined at dead trace location.
1377+
algorithm (int): 1 for old method, 2 for new alternative.
13771378
13781379
Example::
13791380
@@ -1411,7 +1412,8 @@ def slice_cube_window(self, cube, zsurf=None, other=None,
14111412
maskthreshold=maskthreshold,
14121413
snapxy=snapxy,
14131414
showprogress=showprogress,
1414-
deadtraces=deadtraces)
1415+
deadtraces=deadtraces,
1416+
algorithm=algorithm)
14151417
return asurfs
14161418

14171419
# =========================================================================

tests/test_grid3d/test_grid_operations.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ def test_refine_vertically():
131131
grd.to_file('TMP/test_refined_by_3.roff')
132132

133133

134+
134135
def test_refine_vertically_per_zone():
135136
"""Do a grid refinement vertically, via a dict per zone."""
136137

@@ -151,3 +152,18 @@ def test_refine_vertically_per_zone():
151152
grd.refine_vertically(refinement, zoneprop=zone)
152153

153154
grd.to_file('TMP/test_refined_by_dict.roff')
155+
156+
157+
def test_crop_grid():
158+
"""Crop a grid."""
159+
160+
logger.info('Read grid...')
161+
162+
grd = Grid(emegfile)
163+
logger.info('Read grid... done, NLAY is {}'.format(grd.nlay))
164+
logger.info('Read grid...NCOL, NROW, NLAY is {} {} {}'
165+
.format(grd.ncol, grd.nrow, grd.nlay))
166+
167+
grd.do_cropping(((30, 60), (20, 40), (1, 46)))
168+
169+
grd.to_file('TMP/grid_cropped.roff')

tests/test_surface/test_regular_surface_vs_cube.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,48 @@ def test_slice_attr_window_max():
186186
infotext='Method: trilinear, window')
187187

188188

189+
@tsetup.skipsegyio
190+
@tsetup.skipifroxar
191+
def test_slice_attr_window_max_algorithm2():
192+
"""Slice a cube within a window, get max, using trilinear interpol,
193+
alg 2.
194+
"""
195+
196+
logger.info('Loading surface')
197+
xs1 = RegularSurface(rtop1)
198+
xs2 = xs1.copy()
199+
xs3 = xs1.copy()
200+
201+
logger.info('Loading cube')
202+
cc = Cube(rsgy1)
203+
204+
t1 = xtg.timer()
205+
xs1.slice_cube_window(cc, attribute='min', sampling='trilinear',
206+
algorithm=2)
207+
t2 = xtg.timer(t1)
208+
# logger.info('Window slicing... {} secs'. format(t2))
209+
210+
# xs1.quickplot(filename=td + '/surf_slice_cube_window_min.png',
211+
# colortable='seismic', minmax=(-1, 1),
212+
# title='Reek Minimum',
213+
# infotext='Method: trilinear, window')
214+
215+
# xs2.slice_cube_window(cc, attribute='max', sampling='trilinear',
216+
# showprogress=True)
217+
218+
# xs2.quickplot(filename=td + '/surf_slice_cube_window_max.png',
219+
# colortable='seismic', minmax=(-1, 1),
220+
# title='Reek Maximum',
221+
# infotext='Method: trilinear, window')
222+
223+
# xs3.slice_cube_window(cc, attribute='rms', sampling='trilinear')
224+
225+
# xs3.quickplot(filename=td + '/surf_slice_cube_window_rms.png',
226+
# colortable='jet', minmax=(0, 1),
227+
# title='Reek rms (root mean square)',
228+
# infotext='Method: trilinear, window')
229+
230+
189231
@tsetup.skipifroxar
190232
def test_cube_attr_mean_two_surfaces():
191233
"""Get cube attribute (mean) between two surfaces."""

0 commit comments

Comments
 (0)