Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions esmvalcore/preprocessor/_mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,3 +690,49 @@ def _get_fillvalues_mask(cube, threshold_fraction, min_value, time_window):
mask = mask.data | mask.mask

return mask


def mask_generalized(cube, mask_cube, mask_operation):
"""Mask out either landsea (combined) or ice.

Function that masks out data in a given cube, based on a
user-defined operation and a secondary cube (used as mask generator).

Parameters
----------
cube: iris.cube.Cube
data cube to be masked.

mask_cube: iris.cube.Cube
data cube to be used as mask.

mask_operation: str
conditional operation that generates a mask on cube.

Returns
-------
iris.cube.Cube
Returns masked iris cube after applying mask_operation via mask_cube.

Raises
------
ValueError
Error raised if landsea-ice mask not found as an ancillary variable.
"""
if not isinstance(mask_operation, dict):
raise ValueError(f"A valid masking operation dictionary is "
f"needed, got mask_operation {mask_operation}")

# use case 1: mask mask_cube above a certain threshold
# then get its mask and add it to the input cube's one (if any)
if "above_threshold" in mask_operation:
if "threshold" not in mask_operation:
raise KeyError('A valid "threshold" parameter must be specified '
'for above_threshold mask_operation')
threshold = mask_operation["threshold"]
masked_above_th = mask_above_threshold(mask_cube, threshold)

cubes = iris.cube.CubeList([cube, masked_above_th])
cube = _multimodel_mask_cubes(cubes, cube.shape)[0]

return cube
16 changes: 15 additions & 1 deletion tests/unit/preprocessor/_mask/test_mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
import numpy as np

import iris
import iris.fileformats
import tests
from cf_units import Unit
from esmvalcore.preprocessor._mask import (_apply_fx_mask,
count_spells, _get_fx_mask,
mask_above_threshold,
mask_below_threshold,
mask_glaciated, mask_inside_range,
mask_outside_range)
mask_outside_range,
mask_generalized)


class Test(tests.Test):
Expand Down Expand Up @@ -126,6 +128,18 @@ def test_mask_outside_range(self):
expected = np.ma.array(self.data2, mask=[[True, False], [False, True]])
self.assert_array_equal(result.data, expected)

def test_mask_generalized_above_threshold(self):
"""Test mask generalized."""
cube = self.time_cube
mask_cube = self.time_cube
mask_operation = {"above_threshold": True, "threshold": 2.0}
result = mask_generalized(cube, mask_cube, mask_operation)
self.assert_array_equal(result.shape, (24,))
self.assert_array_equal(result.data, cube.data)
mask = np.ones((24), bool)
mask[0:2] = False
self.assert_array_equal(result.data.mask, mask)


if __name__ == '__main__':
unittest.main()