Beispiel #1
0
 def test_basic(self):
     """Test that removing a halo of points from the data on a cube
     has worked as intended."""
     expected = np.array([[0.]])
     width_x = width_y = 2
     padded_cube = remove_halo_from_cube(self.cube, width_x, width_y)
     self.assertIsInstance(padded_cube, iris.cube.Cube)
     self.assertArrayAlmostEqual(padded_cube.data, expected)
Beispiel #2
0
 def test_zero_width(self):
     """Test that removing a halo of points from the data on a cube
     has worked as intended, if a width of zero is specified."""
     expected = np.array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
                          [1., 1., 1., 1., 1., 0., 1., 1., 1., 1.]])
     width_x = 0
     width_y = 4
     padded_cube = remove_halo_from_cube(self.large_cube, width_x, width_y)
     self.assertIsInstance(padded_cube, iris.cube.Cube)
     self.assertArrayAlmostEqual(padded_cube.data, expected)
Beispiel #3
0
 def test_different_widths(self):
     """Test that removing a halo of points from the data on a cube
     has worked as intended for different x and y widths."""
     expected = np.array([[1., 1., 1., 1., 1., 1.],
                          [1., 1., 1., 0., 1., 1.]])
     width_x = 2
     width_y = 4
     padded_cube = remove_halo_from_cube(self.large_cube, width_x, width_y)
     self.assertIsInstance(padded_cube, iris.cube.Cube)
     self.assertArrayAlmostEqual(padded_cube.data, expected)
Beispiel #4
0
    def _remove_padding_and_mask(
            self, neighbourhood_averaged_cube,
            original_cube, mask,
            grid_cells_x, grid_cells_y):
        """
        Remove the halo from the padded array and apply the mask, if required.
        If fraction option set, clip the data so values lie within
        the range of the original cube.

        Args:
            neighbourhood_averaged_cube (iris.cube.Cube):
                Cube containing the smoothed field after the square
                neighbourhood method has been applied.
            original_cube (iris.cube.Cube or None):
                The original cube slice.
            mask (iris.cube.Cube):
                The mask cube created by set_up_cubes_to_be_neighbourhooded.
            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:
            iris.cube.Cube:
                Cube containing the smoothed field after the square
                neighbourhood method has been applied and halo removed.
        """
        # Correct neighbourhood averages for masked data, which may have been
        # calculated using larger neighbourhood areas than are present in
        # reality.
        neighbourhood_averaged_cube = remove_halo_from_cube(
            neighbourhood_averaged_cube, grid_cells_x+1, grid_cells_y+1)
        if self.re_mask and mask.data.min() < 1.0:
            neighbourhood_averaged_cube.data = np.ma.masked_array(
                neighbourhood_averaged_cube.data,
                mask=np.logical_not(mask.data.squeeze()))
        # Add clipping
        if self.sum_or_fraction == "fraction":
            minimum_value = np.nanmin(original_cube.data)
            maximum_value = np.nanmax(original_cube.data)
            neighbourhood_averaged_cube = (
                clip_cube_data(neighbourhood_averaged_cube,
                               minimum_value, maximum_value))
        return neighbourhood_averaged_cube
Beispiel #5
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
Beispiel #6
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
Beispiel #7
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