diff --git a/esmvalcore/preprocessor/_mask.py b/esmvalcore/preprocessor/_mask.py index 741d45686f..edc6247b2d 100644 --- a/esmvalcore/preprocessor/_mask.py +++ b/esmvalcore/preprocessor/_mask.py @@ -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 diff --git a/tests/unit/preprocessor/_mask/test_mask.py b/tests/unit/preprocessor/_mask/test_mask.py index a6b28e2cae..f9e20114fb 100644 --- a/tests/unit/preprocessor/_mask/test_mask.py +++ b/tests/unit/preprocessor/_mask/test_mask.py @@ -5,6 +5,7 @@ import numpy as np import iris +import iris.fileformats import tests from cf_units import Unit from esmvalcore.preprocessor._mask import (_apply_fx_mask, @@ -12,7 +13,8 @@ mask_above_threshold, mask_below_threshold, mask_glaciated, mask_inside_range, - mask_outside_range) + mask_outside_range, + mask_generalized) class Test(tests.Test): @@ -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()