Example #1
0
    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
Example #2
0
 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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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
Example #6
0
    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)
Example #7
0
    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)
Example #8
0
 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
Example #9
0
 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)
Example #10
0
 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)
Example #11
0
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)
        )
Example #12
0
    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
Example #13
0
 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)
Example #14
0
 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)
Example #15
0
 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)
Example #16
0
 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)
Example #17
0
    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)
Example #18
0
    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
Example #19
0
    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)
Example #20
0
    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
Example #21
0
    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
Example #22
0
    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