def test_below_threshold_without_fuzzy_factor(self): """Test if the fixed threshold is above the value in the data.""" plugin = Threshold(0.6) result = plugin.process(self.cube) expected_result_array = np.zeros_like(self.cube.data).reshape( 1, 1, 5, 5) self.assertArrayAlmostEqual(result.data, expected_result_array)
def iterate_over_threshold(self, cubelist, threshold): """ Iterate over the application of thresholding to multiple cubes. Args: cubelist (iris.cube.CubeList): Cubelist containing cubes to be thresholded. threshold (float): The threshold that will be applied. Returns: iris.cube.CubeList: Cubelist after thresholding each cube. """ cubes = iris.cube.CubeList([]) for cube in cubelist: threshold_cube = BasicThreshold( threshold, fuzzy_factor=self.fuzzy_factor, comparison_operator=self.comparison_operator, )(cube.copy()) # Will only ever contain one slice on threshold for cube_slice in threshold_cube.slices_over( find_threshold_coordinate(threshold_cube)): threshold_cube = cube_slice cubes.append(threshold_cube) return cubes
def test_threshold_fuzzy_miss_high_threshold(self): """Test when a point is not within the fuzzy high threshold area.""" plugin = Threshold(3.0, fuzzy_factor=self.fuzzy_factor) result = plugin.process(self.cube) expected_result_array = np.zeros_like(self.cube.data).reshape( 1, 1, 5, 5) self.assertArrayAlmostEqual(result.data, expected_result_array)
class Test__add_threshold_coord(IrisTest): """Test the _add_threshold_coord method""" def setUp(self): """Set up a cube and plugin for testing.""" self.cube = set_up_variable_cube(np.ones((3, 3), dtype=np.float32)) self.plugin = Threshold([1]) self.plugin.threshold_coord_name = self.cube.name() def test_basic(self): """Test a scalar threshold coordinate is created""" result = self.plugin._add_threshold_coord(self.cube, 1) self.assertEqual(result.ndim, 3) self.assertIn("air_temperature", [coord.standard_name for coord in result.coords(dim_coords=True)]) threshold_coord = result.coord("air_temperature") self.assertEqual(threshold_coord.var_name, "threshold") self.assertEqual(threshold_coord.attributes, {"spp__relative_to_threshold": "above"}) self.assertAlmostEqual(threshold_coord.points[0], 1) self.assertEqual(threshold_coord.units, self.cube.units) def test_long_name(self): """Test coordinate is created with non-standard diagnostic name""" self.cube.rename("sky_temperature") self.plugin.threshold_coord_name = self.cube.name() result = self.plugin._add_threshold_coord(self.cube, 1) self.assertIn("sky_temperature", [coord.long_name for coord in result.coords(dim_coords=True)]) def test_value_error(self): """Test method catches ValueErrors unrelated to name, by passing it a list of values where a scalar is required""" with self.assertRaises(ValueError): self.plugin._add_threshold_coord(self.cube, [1, 1])
def test_basic(self): """Test that the plugin returns an iris.cube.Cube.""" fuzzy_factor = 0.95 threshold = 0.1 plugin = Threshold(threshold, fuzzy_factor=fuzzy_factor) result = plugin.process(self.cube) self.assertIsInstance(result, Cube)
def test_threshold_dimension_added(self): """Test that a threshold dimension coordinate is added.""" plugin = Threshold(0.1) result = plugin.process(self.cube) expected_coord = DimCoord([0.1], long_name='threshold', units=self.cube.units) self.assertEqual(result.coord('threshold'), expected_coord)
def test_threshold_below_fuzzy_miss(self): """Test not meeting the threshold in fuzzy below-threshold-mode.""" plugin = Threshold( 2.0, fuzzy_factor=self.fuzzy_factor, below_thresh_ok=True) result = plugin.process(self.cube) expected_result_array = np.ones_like(self.cube.data).reshape( 1, 1, 5, 5) self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold_fuzzy(self): """Test when a point is in the fuzzy threshold area.""" plugin = Threshold(0.6, fuzzy_factor=self.fuzzy_factor) result = plugin.process(self.cube) expected_result_array = np.zeros_like(self.cube.data).reshape( 1, 1, 5, 5) expected_result_array[0][0][2][2] = 1.0/3.0 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold_asymmetric_bounds_below(self): """Test when a point is below asymmetric fuzzy threshold area.""" bounds = (0.51, 0.9) plugin = Threshold(0.6, fuzzy_bounds=bounds) result = plugin.process(self.cube) expected_result_array = np.zeros_like(self.cube.data).reshape( 1, 1, 5, 5) self.assertArrayAlmostEqual(result.data, expected_result_array)
def correct_where_input_true(self, selector_val): """ Replace points in the output_cube where output_land matches the selector_val and the input_land does not match, but has matching points in the vicinity, with the nearest matching point in the vicinity in the original nearest_cube. Updates self.output_cube.data Args: selector_val (int): Value of mask to replace if needed. Intended to be 1 for filling land points near the coast and 0 for filling sea points near the coast. """ # Find all points on output grid matching selector_val use_points = np.where(self.input_land.data == selector_val) # If there are no matching points on the input grid, no alteration can # be made. This tests the size of the y-coordinate of use_points. if use_points[0].size is 0: return # Get shape of output grid ynum, xnum = self.output_land.shape # Using only these points, extrapolate to fill domain using nearest # neighbour. This will generate a grid where the non-selector_val # points are filled with the nearest value in the same mask # classification. (y_points, x_points) = np.mgrid[0:ynum, 0:xnum] selector_data = griddata( use_points, self.nearest_cube.data[use_points], (y_points, x_points), method="nearest", ) # Identify nearby points on regridded input_land that match the # selector_value if selector_val > 0.5: thresholder = BasicThreshold(0.5) else: thresholder = BasicThreshold(0.5, comparison_operator="<=") in_vicinity = self.vicinity(thresholder(self.input_land)) # Identify those points sourced from the opposite mask that are # close to a source point of the correct mask mismatch_points = np.logical_and( np.logical_and( self.output_land.data == selector_val, self.input_land.data != selector_val, ), in_vicinity.data > 0.5, ) # Replace these points with the filled-domain data self.output_cube.data[mismatch_points] = selector_data[mismatch_points]
def test_threshold_asymmetric_bounds_above(self): """Test when a point is above asymmetric fuzzy threshold area.""" bounds = (0.0, 0.45) plugin = Threshold(0.4, fuzzy_bounds=bounds) result = plugin.process(self.cube) expected_result_array = np.zeros_like(self.cube.data).reshape( 1, 1, 5, 5) expected_result_array[0][0][2][2] = 1. self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold_boundingbelowzero(self): """Test fuzzy threshold of below-zero.""" bounds = (-1.0, 1.0) plugin = Threshold(0.0, fuzzy_bounds=bounds, below_thresh_ok=True) result = plugin.process(self.cube) expected_result_array = np.full_like( self.cube.data, fill_value=0.5).reshape(1, 1, 5, 5) expected_result_array[0][0][2][2] = 0.25 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold_boundingzero_above(self): """Test fuzzy threshold of zero where data are above upper-bound.""" bounds = (-0.1, 0.1) plugin = Threshold(0.0, fuzzy_bounds=bounds) result = plugin.process(self.cube) expected_result_array = np.full_like( self.cube.data, fill_value=0.5).reshape(1, 1, 5, 5) expected_result_array[0][0][2][2] = 1. self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold_negative(self): """Test a point when the threshold is negative.""" plugin = Threshold(-1.0, fuzzy_factor=self.fuzzy_factor, below_thresh_ok=True) result = plugin.process(self.cube) expected_result_array = np.ones_like(self.cube.data).reshape( 1, 1, 5, 5) self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold_below_fuzzy(self): """Test a point in fuzzy threshold in below-threshold-mode.""" plugin = Threshold( 0.6, fuzzy_factor=self.fuzzy_factor, below_thresh_ok=True) result = plugin.process(self.cube) expected_result_array = np.ones_like(self.cube.data).reshape( 1, 1, 5, 5) expected_result_array[0][0][2][2] = 2.0/3.0 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_above_threshold_without_fuzzy_factor(self): """Test if the fixed threshold is below the value in the data.""" # Copy the cube as the cube.data is used as the basis for comparison. cube = self.cube.copy() plugin = Threshold(0.1) result = plugin.process(cube) expected_result_array = self.cube.data.reshape(1, 1, 5, 5) expected_result_array[0][0][2][2] = 1.0 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold_point_nan(self): """Test behaviour for a single NaN grid cell.""" # Need to copy the cube as we're adjusting the data. self.cube.data[0][2][2] = np.NAN msg = "NaN detected in input cube data" plugin = Threshold( 2.0, fuzzy_factor=self.fuzzy_factor, below_thresh_ok=True) with self.assertRaisesRegex(ValueError, msg): plugin.process(self.cube)
def test_threshold_asymmetric_bounds_upper_below(self): """Test when a point is in upper asymmetric fuzzy threshold area and below-threshold is requested.""" bounds = (0.0, 0.6) plugin = Threshold(0.4, fuzzy_bounds=bounds, below_thresh_ok=True) result = plugin.process(self.cube) expected_result_array = np.ones_like(self.cube.data).reshape( 1, 1, 5, 5) expected_result_array[0][0][2][2] = 0.25 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold_asymmetric_bounds_middle(self): """Test when a point is on the threshold with asymmetric fuzzy bounds.""" bounds = (0.4, 0.9) plugin = Threshold(0.5, fuzzy_bounds=bounds) result = plugin.process(self.cube) expected_result_array = np.zeros_like(self.cube.data).reshape( 1, 1, 5, 5) expected_result_array[0][0][2][2] = 0.5 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold(self): """Test the basic threshold functionality.""" # Copy the cube as the cube.data is used as the basis for comparison. cube = self.cube.copy() fuzzy_factor = 0.95 plugin = Threshold(0.1, fuzzy_factor=fuzzy_factor) result = plugin.process(cube) # The single 0.5-valued point => 1.0, so cheat by * 2.0 vs orig data. expected_result_array = (self.cube.data * 2.0).reshape(1, 1, 5, 5) self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold_unit_conversion(self): """Test data are correctly thresholded when the threshold is given in units different from that of the input cube. In this test two thresholds (of 4 and 6 mm/h) are used on a 5x5 cube where the central data point value is 1.39e-6 m/s (~ 5 mm/h).""" expected_result_array = np.zeros((2, 5, 5)) expected_result_array[0][2][2] = 1. plugin = Threshold([4.0, 6.0], threshold_units='mm h-1') result = plugin.process(self.rate_cube) self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_masked_array_fuzzybounds(self): """Test masked array are handled correctly when using fuzzy bounds. Masked values are preserved following thresholding.""" bounds = (0.6 * self.fuzzy_factor, 0.6 * (2. - self.fuzzy_factor)) plugin = Threshold(0.6, fuzzy_bounds=bounds) result = plugin.process(self.masked_cube) expected_result_array = self.masked_cube.data.reshape((1, 1, 5, 5)) expected_result_array[0][0][2][2] = 1.0/3.0 self.assertArrayAlmostEqual(result.data.data, expected_result_array) self.assertArrayEqual( result.data.mask, self.masked_cube.data.mask.reshape((1, 1, 5, 5)))
def test_data_precision_preservation(self): """Test that the plugin returns an iris.cube.Cube of the same float precision as the input cube.""" threshold = 0.1 plugin = Threshold(threshold, fuzzy_factor=self.fuzzy_factor) f64cube = self.cube.copy(data=self.cube.data.astype(np.float64)) f32cube = self.cube.copy(data=self.cube.data.astype(np.float32)) f64result = plugin.process(f64cube) f32result = plugin.process(f32cube) self.assertEqual(f64cube.dtype, f64result.dtype) self.assertEqual(f32cube.dtype, f32result.dtype)
def test_threshold_negative(self): """Test a point when the threshold is negative.""" self.cube.data[0][2][2] = -0.75 plugin = Threshold(-1.0, fuzzy_factor=self.fuzzy_factor, comparison_operator='<') result = plugin.process(self.cube) expected_result_array = np.zeros_like(self.cube.data).reshape( 1, 1, 5, 5) expected_result_array[0][0][2][2] = 0.25 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_threshold_unit_conversion_fuzzy_factor(self): """Test for sensible fuzzy factor behaviour when units of threshold are different from input cube. A fuzzy factor of 0.75 is equivalent to bounds +/- 25% around the threshold in the given units. So for a threshold of 4 (6) mm/h, the thresholded exceedance probabilities increase linearly from 0 at 3 (4.5) mm/h to 1 at 5 (7.5) mm/h.""" expected_result_array = np.zeros((2, 5, 5)) expected_result_array[0][2][2] = 1. expected_result_array[1][2][2] = 0.168 plugin = Threshold([4.0, 6.0], threshold_units='mm h-1', fuzzy_factor=0.75) result = plugin.process(self.rate_cube) self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_multiple_thresholds(self): """Test multiple thresholds applied to the cube return a single cube with multiple arrays corresponding to each threshold.""" thresholds = [0.2, 0.4, 0.6] plugin = Threshold(thresholds) result = plugin.process(self.cube) expected_array12 = np.zeros_like(self.cube.data).reshape(1, 1, 5, 5) expected_array12[0][0][2][2] = 1. expected_array3 = expected_array12 * 0. expected_result_array = np.vstack( [expected_array12, expected_array12, expected_array3]) self.assertIsInstance(result, Cube) self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_data_type_change_for_ints(self): """Test that the plugin returns an iris.cube.Cube of float32 type if the input cube is of int type. This allows fuzzy bounds to be used which return fractional values.""" fuzzy_factor = 5./6. threshold = 12 self.cube.data = np.arange(25).reshape(1, 5, 5) plugin = Threshold(threshold, fuzzy_factor=fuzzy_factor) result = plugin.process(self.cube) expected = np.round(np.arange(0, 1, 1./25.)).reshape(1, 1, 5, 5) expected[0, 0, 2, 1:4] = [0.25, 0.5, 0.75] self.assertEqual(result.dtype, 'float32') self.assertArrayEqual(result.data, expected)
def test_multiple_thresholds(self): """Test multiple thresholds applied to the cube return a single cube with multiple arrays corresponding to each threshold.""" thresholds = [0.2, 0.4, 0.6] plugin = Threshold(thresholds) result = plugin.process(self.cube) all_zeroes = np.zeros_like(self.cube.data).reshape(1, 1, 5, 5) one_exceed_point = all_zeroes.copy() one_exceed_point[0][0][2][2] = 1. expected_result_array = np.vstack( [one_exceed_point, one_exceed_point, all_zeroes]) # transpose array to reflect realization coordinate re-ordering expected_result_array = np.transpose(expected_result_array, [1, 0, 2, 3]) self.assertIsInstance(result, Cube) self.assertArrayAlmostEqual(result.data, expected_result_array)
def iterate_over_threshold(self, cubelist, threshold): """ Iterate over the application of thresholding to multiple cubes. Args: cubelist (Iris.cube.CubeList): Cubelist containing cubes to be thresholded. threshold (float): The threshold that will be applied. Returns: cubes (Iris.cube.CubeList): Cubelist after thresholding each cube. """ cubes = iris.cube.CubeList([]) for cube in cubelist: threshold_cube = ( BasicThreshold( threshold, fuzzy_factor=self.fuzzy_factor, below_thresh_ok=self.below_thresh_ok ).process(cube.copy())) # Will only ever contain one slice on threshold for cube_slice in threshold_cube.slices_over('threshold'): threshold_cube = cube_slice cubes.append(threshold_cube) return cubes
def test_threshold_le(self): """Test a point when we are in le threshold mode.""" plugin = Threshold(0.5, comparison_operator='<=') name = "probability_of_{}_below_threshold" expected_name = name.format(self.cube.name()) expected_attribute = "below" result = plugin.process(self.cube) expected_result_array = np.ones_like(self.cube.data).reshape( 1, 1, 5, 5) expected_result_array[0][0][2][2] = 1 self.assertEqual(result.name(), expected_name) self.assertEqual( result.coord( var_name="threshold").attributes['spp__relative_to_threshold'], expected_attribute) self.assertArrayAlmostEqual(result.data, expected_result_array)