def _pad_and_calculate_neighbourhood(self, cube, mask, grid_cells_x, grid_cells_y): """ Apply neighbourhood processing consisting of the following steps: 1. Pad a halo around the input cube to allow vectorised neighbourhooding at edgepoints. 2. Cumulate the array along the x and y axes. 3. Apply neighbourhood processing to the cumulated array. Args: cube (iris.cube.Cube): Cube with masked or NaN values set to 0.0 mask (iris.cube.Cube): Cube with masked or NaN values set to 0.0 grid_cells_x (float): The number of grid cells along the x axis used to create a square neighbourhood. grid_cells_y (float): The number of grid cells along the y axis used to create a square neighbourhood. Returns: neighbourhood_averaged_cube (iris.cube.Cube): Cube containing the smoothed field after the square neighbourhood method has been applied with halo added. """ # Pad the iris cube with the neighbourhood radius plus 1 grid point. # Since the neighbourhood size is 2*radius + 1, this means that all # grid points within the original domain will have data available for # the full neighbourhood size. The halo is removed later. padded_cube = pad_cube_with_halo(cube, grid_cells_x + 1, grid_cells_y + 1, halo_mean_data=False) padded_mask = pad_cube_with_halo(mask, grid_cells_x + 1, grid_cells_y + 1, halo_mean_data=False) # Check whether cube contains complex values is_complex = np.any(np.iscomplex(cube.data)) summed_up_cube = self.cumulate_array(padded_cube, is_complex) summed_up_mask = self.cumulate_array(padded_mask) neighbourhood_averaged_cube = (self.mean_over_neighbourhood( summed_up_cube, summed_up_mask, grid_cells_x, grid_cells_y, is_complex)) if neighbourhood_averaged_cube.dtype in [np.float64, np.longdouble]: neighbourhood_averaged_cube.data = ( neighbourhood_averaged_cube.data.astype(np.float32)) return neighbourhood_averaged_cube
def test_zero_width(self): """Test that padding the data in a cube with a width of zero has worked as intended.""" expected = np.array( [ [0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0], [1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 0.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0], [0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0], ] ) width_x = 0 width_y = 4 padded_cube = pad_cube_with_halo(self.cube, width_x, width_y) self.assertIsInstance(padded_cube, iris.cube.Cube) self.assertArrayAlmostEqual(padded_cube.data, expected)
def test_halo_using_mean_method(self): """Test values in halo are correctly smoothed when using pad_method='mean'. This impacts recursive filter outputs.""" data = np.array( [ [0.0, 0.0, 0.1, 0.0, 0.0], [0.0, 0.0, 0.25, 0.0, 0.0], [0.1, 0.25, 0.5, 0.25, 0.1], [0.0, 0.0, 0.25, 0.0, 0.0], [0.0, 0.0, 0.1, 0.0, 0.0], ], dtype=np.float32, ) self.cube.data = data expected_data = np.array( [ [0.0, 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.0, 0.0], [0.1, 0.1, 0.1, 0.25, 0.5, 0.25, 0.1, 0.1, 0.1], [0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0], ], dtype=np.float32, ) padded_cube = pad_cube_with_halo(self.cube, 2, 2, pad_method="mean") self.assertArrayAlmostEqual(padded_cube.data, expected_data)
def test_halo_using_maximum_method(self): """Test values in halo are correct when using pad_method='maximum'. Note that a larger halo is used as the stat_length used for np.pad is half the halo width, thus to include the 0.5 within stat_length the halo size must be at least 4.""" expected_data = np.array( [ [0.5, 0.5, 0.5, 0.5, 0.1, 0.5, 0.1, 0.5, 0.5, 0.5, 0.5], [0.5, 0.5, 0.5, 0.5, 0.1, 0.5, 0.1, 0.5, 0.5, 0.5, 0.5], [0.5, 0.5, 0.5, 0.5, 0.1, 0.5, 0.1, 0.5, 0.5, 0.5, 0.5], [0.5, 0.5, 0.5, 0.5, 0.1, 0.5, 0.1, 0.5, 0.5, 0.5, 0.5], [0.1, 0.1, 0.1, 0.1, 0.0, 0.1, 0.0, 0.1, 0.1, 0.1, 0.1], [0.5, 0.5, 0.5, 0.5, 0.1, 0.5, 0.1, 0.5, 0.5, 0.5, 0.5], [0.1, 0.1, 0.1, 0.1, 0.0, 0.1, 0.0, 0.1, 0.1, 0.1, 0.1], [0.5, 0.5, 0.5, 0.5, 0.1, 0.5, 0.1, 0.5, 0.5, 0.5, 0.5], [0.5, 0.5, 0.5, 0.5, 0.1, 0.5, 0.1, 0.5, 0.5, 0.5, 0.5], [0.5, 0.5, 0.5, 0.5, 0.1, 0.5, 0.1, 0.5, 0.5, 0.5, 0.5], [0.5, 0.5, 0.5, 0.5, 0.1, 0.5, 0.1, 0.5, 0.5, 0.5, 0.5], ], dtype=np.float32, ) padded_cube = pad_cube_with_halo( self.alternative_cube, 4, 4, pad_method="maximum" ) self.assertArrayAlmostEqual(padded_cube.data, expected_data)
def _set_smoothing_coefficients(self, cube, smoothing_coefficient, smoothing_coefficients_cube): """ Set up the smoothing_coefficient parameter. Args: cube (iris.cube.Cube): 2D cube containing the input data to which the recursive filter will be applied. smoothing_coefficient (float): The constant used to weight the recursive filter in that direction: Defined such that 0.0 < smoothing_coefficient < 1.0 smoothing_coefficients_cube (iris.cube.Cube or None): Cube containing array of smoothing_coefficient values that will be used when applying the recursive filter in a specific direction. Raises: ValueError: If both smoothing_coefficients_cube and smoothing_coefficient are provided. ValueError: If smoothing_coefficient and smoothing_coefficients_cube are both set to None. ValueError: If the dimensions of the smoothing_coefficients array do not match the dimensions of the cube data. Returns: iris.cube.Cube: Cube containing a padded array of smoothing_coefficient values for the specified direction. """ if (smoothing_coefficient is not None and smoothing_coefficients_cube is not None): emsg = ("A cube of smoothing_coefficient values and a single float" " value for smoothing_coefficient have both been provided." " Only one of these options can be set.") raise ValueError(emsg) if smoothing_coefficients_cube is None: if smoothing_coefficient is None: emsg = ("A value for smoothing_coefficient must be set if " "smoothing_coefficients_cube is set to None: " "smoothing_coefficient is currently set as: {}") raise ValueError(emsg.format(smoothing_coefficient)) smoothing_coefficients_cube = cube.copy( data=np.ones(cube.data.shape) * smoothing_coefficient) if smoothing_coefficients_cube is not None: if smoothing_coefficients_cube.data.shape != cube.data.shape: emsg = ("Dimensions of smoothing_coefficients array do not " "match dimensions of data array: {} < {}") raise ValueError( emsg.format(smoothing_coefficients_cube.data.shape, cube.data.shape)) smoothing_coefficients_cube = pad_cube_with_halo( smoothing_coefficients_cube, 2 * self.edge_width, 2 * self.edge_width, pad_method="symmetric", ) return smoothing_coefficients_cube
def test_different_smoothing_coefficients(self): """Test that the _run_recursion method returns expected values when smoothing_coefficient values are different in the x and y directions""" cube = iris.util.squeeze(self.cube) smoothing_coefficient_y = 0.5 * self.smoothing_coefficient_x smoothing_coefficients_x = ( RecursiveFilter()._set_smoothing_coefficients( cube, self.smoothing_coefficient_x, None)) smoothing_coefficients_y = ( RecursiveFilter()._set_smoothing_coefficients( cube, smoothing_coefficient_y, None)) padded_cube = pad_cube_with_halo(cube, 2, 2) result = RecursiveFilter()._run_recursion(padded_cube, smoothing_coefficients_x, smoothing_coefficients_y, 1) # slice back down to the source grid - easier to visualise! unpadded_result = result.data[2:-2, 2:-2] expected_result = np.array( [[0.01620921, 0.02866841, 0.05077430, 0.02881413, 0.01657352], [0.03978802, 0.06457599, 0.10290188, 0.06486591, 0.04051282], [0.10592333, 0.15184643, 0.19869247, 0.15238355, 0.10726611], [0.03978982, 0.06457873, 0.10290585, 0.06486866, 0.04051464], [0.01621686, 0.02868005, 0.05079120, 0.02882582, 0.01658128]]) self.assertArrayAlmostEqual(unpadded_result, expected_result)
def test_different_smoothing_coefficients(self): """Test that the _run_recursion method returns expected values when smoothing_coefficient values are different in the x and y directions""" edge_width = 1 cube = iris.util.squeeze(self.cube) smoothing_coefficients_x, smoothing_coefficients_y = RecursiveFilter( edge_width=edge_width )._pad_coefficients(*self.smoothing_coefficients_alternative) padded_cube = pad_cube_with_halo(cube, 2 * edge_width, 2 * edge_width) result = RecursiveFilter(edge_width=edge_width)._run_recursion( padded_cube, smoothing_coefficients_x, smoothing_coefficients_y, 1 ) # slice back down to the source grid - easier to visualise! unpadded_result = result.data[2:-2, 2:-2] expected_result = np.array( [ [0.01320939, 0.02454378, 0.04346254, 0.02469828, 0.01359563], [0.03405095, 0.06060188, 0.09870366, 0.06100013, 0.03504659], [0.0845406, 0.13908109, 0.18816182, 0.14006987, 0.08701254], [0.03405397, 0.06060749, 0.09871361, 0.06100579, 0.03504971], [0.01322224, 0.02456765, 0.04350482, 0.0247223, 0.01360886], ], dtype=np.float32, ) self.assertArrayAlmostEqual(unpadded_result, expected_result)
def _pad_coefficients(self, coeff_x, coeff_y): """Pad smoothing coefficients""" pad_x, pad_y = [ pad_cube_with_halo( coeff, 2 * self.edge_width, 2 * self.edge_width, pad_method="symmetric", ) for coeff in [coeff_x, coeff_y] ] return pad_x, pad_y
def test_return_type(self): """Test that the _run_recursion method returns an iris.cube.Cube.""" edge_width = 1 cube = iris.util.squeeze(self.cube) alphas_x = RecursiveFilter()._set_alphas(cube, self.alpha_x, None) alphas_y = RecursiveFilter()._set_alphas(cube, self.alpha_y, None) padded_cube = pad_cube_with_halo(cube, 2 * edge_width, 2 * edge_width) result = RecursiveFilter()._run_recursion(padded_cube, alphas_x, alphas_y, self.iterations) self.assertIsInstance(result, Cube)
def test_result_basic(self): """Test that the _run_recursion method returns the expected value.""" edge_width = 1 cube = iris.util.squeeze(self.cube) alphas_x = RecursiveFilter()._set_alphas(cube, self.alpha_x, None) alphas_y = RecursiveFilter()._set_alphas(cube, self.alpha_y, None) padded_cube = pad_cube_with_halo(cube, 2 * edge_width, 2 * edge_width) result = RecursiveFilter()._run_recursion(padded_cube, alphas_x, alphas_y, self.iterations) expected_result = 0.13382206 self.assertAlmostEqual(result.data[4][4], expected_result)
def test_target_domain_bigger_than_source_domain(regridder, landmask, maskedinput): """Test regridding when target domain is bigger than source domain""" # set up source cube, target cube and land-sea mask cube cube_in, cube_out_mask, cube_in_mask = define_source_target_grid_data_same_domain() # add a circle of grid points so that output domain is much bigger than input domain width_x, width_y = 2, 4 # lon,lat cube_out_mask_pad = pad_cube_with_halo(cube_out_mask, width_x, width_y) if landmask: with_mask = "-with-mask" else: with_mask = "" cube_in_mask = None regrid_mode = f"{regridder}{with_mask}-2" if maskedinput: # convert the input data to a masked array with no values covered by the mask cube_in_masked_data = np.ma.masked_array(cube_in.data, mask=False) cube_in.data = cube_in_masked_data # run the regridding regridderLandSea = RegridLandSea( regrid_mode=regrid_mode, landmask=cube_in_mask, landmask_vicinity=250000000, ) regrid_out = regridderLandSea(cube_in, cube_out_mask) regrid_out_pad = regridderLandSea(cube_in, cube_out_mask_pad) # check that results inside the padding matches the same regridding without padding np.testing.assert_allclose( regrid_out.data, regrid_out_pad.data[width_y:-width_y, width_x:-width_x], ) # check results in the padded area if maskedinput: # masked array input should result in masked array output assert hasattr(regrid_out_pad.data, "mask") assert regrid_out_pad.dtype == np.float32 regrid_out_pad.data.mask[width_y:-width_y, width_x:-width_x] = True np.testing.assert_array_equal( regrid_out_pad.data.mask, np.full_like(regrid_out_pad.data, True, dtype=np.bool), ) else: assert not hasattr(regrid_out_pad.data, "mask") assert regrid_out_pad.dtype == np.float32 # fill the area inside the padding with NaNs regrid_out_pad.data[width_y:-width_y, width_x:-width_x] = np.nan # this should result in the whole grid being NaN np.testing.assert_array_equal( regrid_out_pad.data, np.full_like(regrid_out_pad.data, np.nan) )
def _set_alphas(self, cube, alpha, alphas_cube): """ Set up the alpha parameter. Args: cube (Iris.cube.Cube): 2D cube containing the input data to which the recursive filter will be applied. alpha (Float): The constant used to weight the recursive filter in that direction: Defined such that 0.0 < alpha < 1.0 alphas_cube (Iris.cube.Cube or None): Cube containing array of alpha values that will be used when applying the recursive filter in a specific direction. Raises: ValueError: If both alphas_cube and alpha are provided. ValueError: If alpha and alphas_cube are both set to None ValueError: If dimension of alphas array is less than dimension of data array ValueError: If dimension of alphas array is greater than dimension of data array Returns: alphas_cube (Iris.cube.Cube): Cube containing a padded array of alpha values for the specified direction. """ if alpha is not None and alphas_cube is not None: emsg = ("A cube of alpha values and a single float value for alpha" " have both been provded. Only one of these options can be" " set.") raise ValueError(emsg) if alphas_cube is None: if alpha is None: emsg = ("A value for alpha must be set if alphas_cube is " "set to None: alpha is currently set as: {}") raise ValueError(emsg.format(alpha)) alphas_cube = cube.copy( data=np.ones(cube.data.shape) * alpha) if alphas_cube is not None: if alphas_cube.data.shape != cube.data.shape: emsg = ("Dimensions of alphas array do not match dimensions " "of data array: {} < {}") raise ValueError(emsg.format(alphas_cube.data.shape, cube.data.shape)) alphas_cube = pad_cube_with_halo( alphas_cube, 2*self.edge_width, 2*self.edge_width) return alphas_cube
def test_return_type(self): """Test that the _run_recursion method returns an iris.cube.Cube.""" edge_width = 1 cube = iris.util.squeeze(self.cube) smoothing_coefficients_x, smoothing_coefficients_y = RecursiveFilter( edge_width=edge_width )._pad_coefficients(*self.smoothing_coefficients) padded_cube = pad_cube_with_halo(cube, 2 * edge_width, 2 * edge_width) result = RecursiveFilter(edge_width=1)._run_recursion( padded_cube, smoothing_coefficients_x, smoothing_coefficients_y, self.iterations, ) self.assertIsInstance(result, Cube)
def test_result_basic(self): """Test that the _run_recursion method returns the expected value.""" edge_width = 1 cube = iris.util.squeeze(self.cube) smoothing_coefficients_x, smoothing_coefficients_y = RecursiveFilter( edge_width=edge_width )._pad_coefficients(*self.smoothing_coefficients) padded_cube = pad_cube_with_halo(cube, 2 * edge_width, 2 * edge_width) result = RecursiveFilter(edge_width=edge_width)._run_recursion( padded_cube, smoothing_coefficients_x, smoothing_coefficients_y, self.iterations, ) expected_result = 0.12302627 self.assertAlmostEqual(result.data[4][4], expected_result)
def test_basic(self): """Test that padding the data in a cube with a halo has worked as intended when using the default option, which is to use zeroes.""" expected = np.array([ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ]) width_x = width_y = 2 padded_cube = pad_cube_with_halo(self.cube, width_x, width_y) self.assertIsInstance(padded_cube, iris.cube.Cube) self.assertArrayAlmostEqual(padded_cube.data, expected)
def test_basic(self): """Test that padding the data in a cube with a halo has worked as intended.""" expected = np.array( [[0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 1., 1., 1., 1., 0., 0.], [0., 0., 1., 1., 1., 1., 1., 0., 0.], [0., 0., 1., 1., 0., 1., 1., 0., 0.], [0., 0., 1., 1., 1., 1., 1., 0., 0.], [0., 0., 1., 1., 1., 1., 1., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0.]]) width_x = width_y = 2 padded_cube = pad_cube_with_halo( self.cube, width_x, width_y, halo_mean_data=False) self.assertIsInstance(padded_cube, iris.cube.Cube) self.assertArrayAlmostEqual(padded_cube.data, expected)
def test_halo_using_symmetric_method(self): """Test values in halo are correct when using pad_method='symmetric'.""" expected_data = np.array( [ [0.5, 0.1, 0.1, 0.5, 0.1, 0.1, 0.5], [0.1, 0.0, 0.0, 0.1, 0.0, 0.0, 0.1], [0.1, 0.0, 0.0, 0.1, 0.0, 0.0, 0.1], [0.5, 0.1, 0.1, 0.5, 0.1, 0.1, 0.5], [0.1, 0.0, 0.0, 0.1, 0.0, 0.0, 0.1], [0.1, 0.0, 0.0, 0.1, 0.0, 0.0, 0.1], [0.5, 0.1, 0.1, 0.5, 0.1, 0.1, 0.5], ], dtype=np.float32, ) padded_cube = pad_cube_with_halo( self.alternative_cube, 2, 2, pad_method="symmetric" ) self.assertArrayAlmostEqual(padded_cube.data, expected_data)
def _set_smoothing_coefficients(self, smoothing_coefficients_cube): """ Set up the smoothing_coefficient parameter. Args: smoothing_coefficients_cube (iris.cube.Cube): Cube containing array of smoothing_coefficient values that will be used when applying the recursive filter in a specific direction. Returns: iris.cube.Cube: Cube containing a padded array of smoothing_coefficient values for the specified direction. """ smoothing_coefficients_cube = pad_cube_with_halo( smoothing_coefficients_cube, 2 * self.edge_width, 2 * self.edge_width, pad_method="symmetric", ) return smoothing_coefficients_cube
def test_halo_using_mean_smoothing(self): """Test values in halo are correctly smoothed when halo_mean_data=True. This impacts recursive filter outputs.""" data = np.array([[0., 0., 0.1, 0., 0.], [0., 0., 0.25, 0., 0.], [0.1, 0.25, 0.5, 0.25, 0.1], [0., 0., 0.25, 0., 0.], [0., 0., 0.1, 0., 0.]], dtype=np.float32) self.cube.data = data expected_data = np.array( [[0., 0., 0., 0., 0.1, 0., 0., 0., 0.], [0., 0., 0., 0., 0.1, 0., 0., 0., 0.], [0., 0., 0., 0., 0.1, 0., 0., 0., 0.], [0., 0., 0., 0., 0.25, 0., 0., 0., 0.], [0.1, 0.1, 0.1, 0.25, 0.5, 0.25, 0.1, 0.1, 0.1], [0., 0., 0., 0., 0.25, 0., 0., 0., 0.], [0., 0., 0., 0., 0.1, 0., 0., 0., 0.], [0., 0., 0., 0., 0.1, 0., 0., 0., 0.], [0., 0., 0., 0., 0.1, 0., 0., 0., 0.]], dtype=np.float32) padded_cube = pad_cube_with_halo(self.cube, 2, 2) self.assertArrayAlmostEqual(padded_cube.data, expected_data)
def process( self, cube, smoothing_coefficients_x, smoothing_coefficients_y, mask_cube=None, ): """ Set up the smoothing_coefficient parameters and run the recursive filter. Smoothing coefficients can be generated using :func:`~improver.utilities.ancillary_creation.OrographicSmoothingCoefficients` and :func:`~improver.cli.generate_orographic_smoothing_coefficients`. The steps undertaken are: 1. Split the input cube into slices determined by the co-ordinates in the x and y directions. 2. Construct an array of filter parameters (smoothing_coefficients_x and smoothing_coefficients_y) for each cube slice that are used to weight the recursive filter in the x- and y-directions. 3. Pad each cube slice with a square-neighbourhood halo and apply the recursive filter for the required number of iterations. 4. Remove the halo from the cube slice and append the recursed cube slice to a 'recursed cube'. 5. Merge all the cube slices in the 'recursed cube' into a 'new cube'. 6. Modify the 'new cube' so that its scalar dimension co-ordinates are consistent with those in the original input cube. 7. Return the 'new cube' which now contains the recursively filtered values for the original input cube. The smoothing_coefficient determines how much "value" of a cell undergoing filtering is comprised of the current value at that cell and how much comes from the adjacent cell preceding it in the direction in which filtering is being applied. A larger smoothing_coefficient results in a more significant proportion of a cell's new value coming from its neighbouring cell. Args: cube (iris.cube.Cube): Cube containing the input data to which the recursive filter will be applied. smoothing_coefficients_x (iris.cube.Cube): Cube containing array of smoothing_coefficient values that will be used when applying the recursive filter along the x-axis. smoothing_coefficients_y (iris.cube.Cube): Cube containing array of smoothing_coefficient values that will be used when applying the recursive filter along the y-axis. mask_cube (iris.cube.Cube or None): Cube containing an external mask to apply to the cube before applying the recursive filter. Returns: iris.cube.Cube: Cube containing the smoothed field after the recursive filter method has been applied. Raises: ValueError: If any smoothing_coefficient cube value is over 0.5 """ for smoothing_coefficient in ( smoothing_coefficients_x, smoothing_coefficients_y, ): if (smoothing_coefficient.data > 0.5).any(): raise ValueError( "All smoothing_coefficient values must be less than 0.5. " "A large smoothing_coefficient value leads to poor " "conservation of probabilities") cube_format = next( cube.slices([cube.coord(axis="y"), cube.coord(axis="x")])) self._validate_smoothing_coefficients(cube_format, smoothing_coefficients_x) smoothing_coefficients_x = self._set_smoothing_coefficients( smoothing_coefficients_x) self._validate_smoothing_coefficients(cube_format, smoothing_coefficients_y) smoothing_coefficients_y = self._set_smoothing_coefficients( smoothing_coefficients_y) recursed_cube = iris.cube.CubeList() for output in cube.slices([cube.coord(axis="y"), cube.coord(axis="x")]): # Setup cube and mask for processing. # This should set up a mask full of 1.0 if None is provided # and set the data 0.0 where mask is 0.0 or the data is NaN output, mask, nan_array = self.set_up_cubes(output, mask_cube) mask = mask.data.squeeze() padded_cube = pad_cube_with_halo(output, 2 * self.edge_width, 2 * self.edge_width, pad_method="symmetric") new_cube = self._run_recursion( padded_cube, smoothing_coefficients_x, smoothing_coefficients_y, self.iterations, ) new_cube = remove_halo_from_cube(new_cube, 2 * self.edge_width, 2 * self.edge_width) if self.re_mask: new_cube.data[nan_array] = np.nan new_cube.data = np.ma.masked_array(new_cube.data, mask=np.logical_not(mask), copy=False) recursed_cube.append(new_cube) new_cube = recursed_cube.merge_cube() new_cube = check_cube_coordinates(cube, new_cube) return new_cube
def process(self, cube, alphas_x=None, alphas_y=None, mask_cube=None): """ Set up the alpha parameters and run the recursive filter. The steps undertaken are: 1. Split the input cube into slices determined by the co-ordinates in the x and y directions. 2. Construct an array of filter parameters (alphas_x and alphas_y) for each cube slice that are used to weight the recursive filter in the x- and y-directions. 3. Pad each cube slice with a square-neighbourhood halo and apply the recursive filter for the required number of iterations. 4. Remove the halo from the cube slice and append the recursed cube slice to a 'recursed cube'. 5. Merge all the cube slices in the 'recursed cube' into a 'new cube'. 6. Modify the 'new cube' so that its scalar dimension co-ordinates are consistent with those in the original input cube. 7. Return the 'new cube' which now contains the recursively filtered values for the original input cube. Args: cube (iris.cube.Cube): Cube containing the input data to which the recursive filter will be applied. alphas_x (iris.cube.Cube or None): Cube containing array of alpha values that will be used when applying the recursive filter along the x-axis. alphas_y (iris.cube.Cube or None): Cube containing array of alpha values that will be used when applying the recursive filter along the y-axis. mask_cube (iris.cube.Cube or None): Cube containing an external mask to apply to the cube before applying the recursive filter. Returns: iris.cube.Cube: Cube containing the smoothed field after the recursive filter method has been applied. Raises: ValueError: If any alpha cube value is over 0.5 """ for alpha in (alphas_x, alphas_y): if alpha is not None and (alpha.data > 0.5).any(): raise ValueError( "All alpha values must be less than 0.5. A large alpha" "value leads to poor conservation of probabilities") cube_format = next( cube.slices([cube.coord(axis='y'), cube.coord(axis='x')])) alphas_x = self._set_alphas(cube_format, self.alpha_x, alphas_x) alphas_y = self._set_alphas(cube_format, self.alpha_y, alphas_y) recursed_cube = iris.cube.CubeList() for output in cube.slices([cube.coord(axis='y'), cube.coord(axis='x')]): # Setup cube and mask for processing. # This should set up a mask full of 1.0 if None is provided # and set the data 0.0 where mask is 0.0 or the data is NaN output, mask, nan_array = ( SquareNeighbourhood().set_up_cubes_to_be_neighbourhooded( output, mask_cube)) mask = mask.data.squeeze() padded_cube = pad_cube_with_halo(output, 2 * self.edge_width, 2 * self.edge_width) new_cube = self._run_recursion(padded_cube, alphas_x, alphas_y, self.iterations) new_cube = remove_halo_from_cube(new_cube, 2 * self.edge_width, 2 * self.edge_width) if self.re_mask: new_cube.data[nan_array.astype(bool)] = np.nan new_cube.data = np.ma.masked_array(new_cube.data, mask=np.logical_not(mask)) recursed_cube.append(new_cube) new_cube = recursed_cube.merge_cube() new_cube = check_cube_coordinates(cube, new_cube) return new_cube
def process(self, cube: Cube, smoothing_coefficients: CubeList) -> Cube: """ Set up the smoothing_coefficient parameters and run the recursive filter. Smoothing coefficients can be generated using :class:`~.OrographicSmoothingCoefficients` and :func:`~improver.cli.generate_orographic_smoothing_coefficients`. The steps undertaken are: 1. Split the input cube into slices determined by the co-ordinates in the x and y directions. 2. Construct an array of filter parameters (smoothing_coefficients_x and smoothing_coefficients_y) for each cube slice that are used to weight the recursive filter in the x- and y-directions. 3. Pad each cube slice with a square-neighbourhood halo and apply the recursive filter for the required number of iterations. 4. Remove the halo from the cube slice and append the recursed cube slice to a 'recursed cube'. 5. Merge all the cube slices in the 'recursed cube' into a 'new cube'. 6. Modify the 'new cube' so that its scalar dimension co-ordinates are consistent with those in the original input cube. 7. Return the 'new cube' which now contains the recursively filtered values for the original input cube. The smoothing_coefficient determines how much "value" of a cell undergoing filtering is comprised of the current value at that cell and how much comes from the adjacent cell preceding it in the direction in which filtering is being applied. A larger smoothing_coefficient results in a more significant proportion of a cell's new value coming from its neighbouring cell. Args: cube: Cube containing the input data to which the recursive filter will be applied. smoothing_coefficients: A cubelist containing two cubes of smoothing_coefficient values, one corresponding to smoothing in the x-direction, and the other to smoothing in the y-direction. Returns: Cube containing the smoothed field after the recursive filter method has been applied. Raises: ValueError: If the cube contains masked data from multiple cycles or times """ cube_format = next( cube.slices([cube.coord(axis="y"), cube.coord(axis="x")])) coeffs_x, coeffs_y = self._validate_coefficients( cube_format, smoothing_coefficients) mask_cube = None if np.ma.is_masked(cube.data): # Assumes mask is the same for each x-y slice. This may not be # true if there are several time slices in the cube - so throw # an error if this is so. for coord in TIME_COORDS: if cube.coords(coord) and len(cube.coord(coord).points) > 1: raise ValueError( "Dealing with masks from multiple time points is unsupported" ) mask_cube = cube_format.copy(data=cube_format.data.mask) coeffs_x, coeffs_y = self._update_coefficients_from_mask( coeffs_x, coeffs_y, mask_cube, ) padded_coefficients_x, padded_coefficients_y = self._pad_coefficients( coeffs_x, coeffs_y) recursed_cube = iris.cube.CubeList() for output in cube.slices([cube.coord(axis="y"), cube.coord(axis="x")]): padded_cube = pad_cube_with_halo(output, 2 * self.edge_width, 2 * self.edge_width, pad_method="symmetric") new_cube = self._run_recursion( padded_cube, padded_coefficients_x, padded_coefficients_y, self.iterations, ) new_cube = remove_halo_from_cube(new_cube, 2 * self.edge_width, 2 * self.edge_width) if mask_cube is not None: new_cube.data = np.ma.MaskedArray(new_cube.data, mask=mask_cube.data) recursed_cube.append(new_cube) new_cube = recursed_cube.merge_cube() new_cube = check_cube_coordinates(cube, new_cube) return new_cube