Skip to content

Commit ee9361d

Browse files
committed
BUG: fix compute_attributes_in_window() when input cube has trace_id_codes=2 present
The trace_id_codes=2 means "dead traces"
1 parent 0cbb4d5 commit ee9361d

File tree

2 files changed

+60
-4
lines changed

2 files changed

+60
-4
lines changed

src/xtgeo/cube/_cube_window_attributes.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class CubeAttrs:
6363
_upper: RegularSurface | None = None # upper surf, resampled to cube map resolution
6464
_lower: RegularSurface | None = None # lower surf, resampled to cube map resolution
6565
_min_thickness_mask: RegularSurface | None = None # mask for min. thickness trunc.
66+
_mask_map_by_traceidcode: RegularSurface | None = None # mask for traceidcode 2
6667

6768
_result_attr_maps: dict = field(default_factory=dict) # holds the resulting maps
6869

@@ -94,6 +95,7 @@ def _process_upper_lower_surface(self) -> None:
9495
else self.lower_surface
9596
)
9697

98+
# the template surface is the topology that defines the resulting attribute maps
9799
self._template_surface = (
98100
upper
99101
if isinstance(self.upper_surface, (float, int))
@@ -184,7 +186,7 @@ def _create_reduced_cube(self) -> None:
184186
from xtgeo import Cube # avoid circular import by having this here
185187

186188
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
189+
cubev[self.cube.traceidcodes == 2] = 0.0 # set traceidcode 2 to zero
188190

189191
# Create a boolean mask based on the threshold
190192
mask = self._depth_array < self._outside_depth
@@ -311,13 +313,20 @@ def _add_to_attribute_map(self, attr_name: str, values: np.ndarray) -> None:
311313
attr_map = self._upper.copy()
312314
attr_map.values = np.ma.masked_invalid(values)
313315

316+
# apply mask for the cube's dead traces (traceidcode 2)
317+
attr_map.values = np.ma.masked_where(
318+
self.cube.traceidcodes == 2, attr_map.values
319+
)
320+
314321
# now resample to the original input map
315322
attr_map_resampled = self._template_surface.copy()
316323
attr_map_resampled.resample(attr_map)
317324

318-
attr_map_resampled.values = np.ma.masked_where(
319-
self.upper_surface.values.mask, attr_map_resampled.values
320-
)
325+
# Use template_surface consistently for masking (it's already set correctly)
326+
if hasattr(self._template_surface.values, "mask"):
327+
attr_map_resampled.values = np.ma.masked_where(
328+
self._template_surface.values.mask, attr_map_resampled.values
329+
)
321330

322331
self._result_attr_maps[attr_name] = attr_map_resampled
323332

tests/test_surface/test_surface_cube_attrs.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pathlib
22
import warnings
33

4+
import numpy as np
45
import pytest
56

67
import xtgeo
@@ -11,6 +12,7 @@
1112

1213
SFILE1 = pathlib.Path("cubes/etc/ib_synth_iainb.segy")
1314
SFILE2 = pathlib.Path("cubes/reek/syntseis_20030101_seismic_depth_stack.segy")
15+
SFILE3 = pathlib.Path("cubes/etc/cube_w_deadtraces.segy")
1416

1517
TOP2A = pathlib.Path("surfaces/reek/2/01_topreek_rota.gri")
1618
TOP2B = pathlib.Path("surfaces/reek/2/04_basereek_rota.gri")
@@ -35,6 +37,13 @@ def fixture_loadsfile2(testdata_path):
3537
return xtgeo.cube_from_file(testdata_path / SFILE2)
3638

3739

40+
@pytest.fixture(name="loadsfile3")
41+
def fixture_loadsfile3(testdata_path):
42+
"""Fixture for loading a SFILE3"""
43+
logger.info("Load seismic file 3")
44+
return xtgeo.cube_from_file(testdata_path / SFILE3)
45+
46+
3847
def test_single_slice_yflip_snapxy_both(loadsfile1):
3948
"""Test cube values with exact location of maps, compared to cube samples"""
4049
cube1 = loadsfile1
@@ -517,3 +526,41 @@ def test_warn_errs_new_module_reek(loadsfile1, loadsfile2, testdata_path):
517526

518527
with pytest.raises(ValueError, match="no valid data in the interval"):
519528
_ = cube2.compute_attributes_in_window(surf1, surf1, ndiv=4) # same surface
529+
530+
531+
def test_compute_attrs_handling_of_dead_traces(loadsfile3):
532+
"""Test handling of dead traces in the cube."""
533+
cube = loadsfile3
534+
535+
# Create a surface that is above the dead traces
536+
surf = xtgeo.surface_from_cube(cube, 1000.0)
537+
538+
# Compute attributes in window
539+
attrs = cube.compute_attributes_in_window(surf, surf + 10)
540+
541+
# Check if the result is as expected
542+
assert "mean" in attrs
543+
assert attrs["mean"].values.mean() == pytest.approx(239.18202, abs=1e-4)
544+
545+
# count number of dead traces in cube
546+
dead_traces = (cube.traceidcodes == 2).sum()
547+
548+
# count number of masked values in map
549+
masked_values = np.ma.count_masked(attrs["mean"].values)
550+
551+
assert dead_traces == masked_values, (
552+
"Number of dead traces does not match masked values"
553+
)
554+
555+
556+
def test_attribute_around_constant_cube_slices(loadsfile2):
557+
"""Get attribute around a constant cube slices."""
558+
559+
mycube = loadsfile2
560+
561+
level1 = 1710
562+
level2 = 1730
563+
564+
myattrs = mycube.compute_attributes_in_window(level1, level2)
565+
566+
assert myattrs["mean"].values.mean() == pytest.approx(-0.00293736, abs=1e-5)

0 commit comments

Comments
 (0)