Esempio n. 1
0
def convert_cube_data_to_2d(forecast: Cube,
                            coord: str = "realization",
                            transpose: bool = True) -> ndarray:
    """
    Function to convert data from a N-dimensional cube into a 2d
    numpy array. The result can be transposed, if required.

    Args:
        forecast:
            N-dimensional cube to be reshaped.
        coord:
            This dimension is retained as the second dimension by default,
            and the leading dimension if "transpose" is set to False.
        transpose:
            If True, the resulting flattened data is transposed.
            This will transpose a 2d array of the format [coord, :]
            to [:, coord].  If coord is not a dimension on the input cube,
            the resulting array will be 2d with items of length 1.

    Returns:
        Reshaped 2d array.
    """
    forecast_data = []
    if np.ma.is_masked(forecast.data):
        forecast.data = np.ma.filled(forecast.data, np.nan)

    for coord_slice in forecast.slices_over(coord):
        forecast_data.append(coord_slice.data.flatten())
    if transpose:
        forecast_data = np.asarray(forecast_data).T
    return np.array(forecast_data)
Esempio n. 2
0
    def collapse_mask_coord(self, cube: Cube) -> Cube:
        """
        Collapse the chosen coordinate with the available weights. The result
        of the neighbourhood processing is taken into account to renormalize
        any weights corresponding to a NaN in the result from neighbourhooding.
        In this case the weights are re-normalized so that we do not lose
        probability.

        Args:
            cube:
                Cube containing the array to which the square neighbourhood
                with a mask has been applied.
                Dimensions self.coord_for_masking, y and x.

        Returns:
            Cube containing the weighted mean from neighbourhood after
            collapsing the chosen coordinate.
        """
        # Mask out any NaNs in the neighbourhood data so that Iris ignores
        # them when calculating the weighted mean.
        cube.data = ma.masked_invalid(cube.data, copy=False)
        # Collapse the coord_for_masking. Renormalization of the weights happen
        # within the underlying call to a numpy function within the Iris method.
        result = collapsed(
            cube,
            self.coord_for_masking,
            iris.analysis.MEAN,
            weights=self.collapse_weights.data,
        )
        # Set masked invalid data points back to np.nans
        if np.ma.is_masked(result.data):
            result.data.data[result.data.mask] = np.nan
        # Remove references to self.coord_masked in the result cube.
        result.remove_coord(self.coord_for_masking)
        return result
Esempio n. 3
0
    def _rescale_unmasked_weights(self, weights: Cube,
                                  is_rescaled: Cube) -> None:
        """Increase weights of unmasked slices at locations where masked slices
        have been smoothed, so that the sum of weights over self.blend_coord is
        re-normalised (sums to 1) at each point and the relative weightings of
        multiple unmasked slices are preserved.  Modifies weights cube in place.

        Args:
            weights:
                Cube of weights to which fuzzy smoothing has been applied to any
                masked slices
            is_rescaled:
                Cube matching weights.shape, with value of 1 where masked weights
                have been rescaled, and 0 where they are unchanged.
        """
        rescaled_data = np.multiply(weights.data, is_rescaled.data)
        unscaled_data = np.multiply(weights.data, ~is_rescaled.data)
        unscaled_sum = np.sum(unscaled_data, axis=self.blend_axis)
        required_sum = 1.0 - np.sum(rescaled_data, axis=self.blend_axis)
        normalisation_factor = np.where(unscaled_sum > 0,
                                        np.divide(required_sum, unscaled_sum),
                                        0)
        normalised_weights = (
            np.multiply(unscaled_data, normalisation_factor) + rescaled_data)
        weights.data = normalised_weights.astype(FLOAT_DTYPE)
Esempio n. 4
0
def merge_land_and_sea(calibrated_land_only: Cube, uncalibrated: Cube) -> None:
    """
    Merge data that has been calibrated over the land with uncalibrated data.
    Calibrated data will have masked data over the sea which will need to be
    filled with the uncalibrated data.

    Args:
        calibrated_land_only:
            A cube that has been calibrated over the land, with sea points
            masked out. Either realizations, probabilities or percentiles.
            Data is modified in place.
        uncalibrated:
            A cube of uncalibrated data with valid data over the sea. Either
            realizations, probabilities or percentiles. Dimension coordinates
            must be the same as the calibrated_land_only cube.

    Raises:
        ValueError: If input cubes do not have the same input dimensions.
    """
    # Check dimensions the same on both cubes.
    if calibrated_land_only.dim_coords != uncalibrated.dim_coords:
        message = "Input cubes do not have the same dimension coordinates"
        raise ValueError(message)
    # Merge data if calibrated_land_only data is masked.
    if np.ma.is_masked(calibrated_land_only.data):
        new_data = calibrated_land_only.data.data
        mask = calibrated_land_only.data.mask
        new_data[mask] = uncalibrated.data[mask]
        calibrated_land_only.data = new_data
Esempio n. 5
0
    def _normalise_initial_weights(self, weights: Cube) -> None:
        """Normalise weights so that they add up to 1 along the blend dimension
        at each spatial point.  This is different from the normalisation that
        happens after the application of fuzzy smoothing near mask boundaries.
        Modifies weights cube in place.  Array broadcasting relies on blend_coord
        being the leading dimension, as enforced in self._create_template_slice.

        Args:
            weights:
                3D weights containing zeros for masked points, but before
                fuzzy smoothing
        """
        weights_sum = np.sum(weights.data, axis=self.blend_axis)
        weights.data = np.where(weights_sum > 0,
                                np.divide(weights.data, weights_sum),
                                0).astype(FLOAT_DTYPE)
Esempio n. 6
0
    def process(standard_landmask: Cube) -> Cube:
        """Read in the interpolated landmask and round values < 0.5 to False
             and values >=0.5 to True.

        Args:
            standard_landmask:
                input landmask on standard grid.

        Returns:
            output landmask of boolean values.
        """
        mask_sea = standard_landmask.data < 0.5
        standard_landmask.data[mask_sea] = False
        mask_land = standard_landmask.data > 0.0
        standard_landmask.data[mask_land] = True
        standard_landmask.data = standard_landmask.data.astype(np.int8)
        standard_landmask.rename("land_binary_mask")
        return standard_landmask
Esempio n. 7
0
    def _run_recursion(
        cube: Cube,
        smoothing_coefficients_x: Cube,
        smoothing_coefficients_y: Cube,
        iterations: int,
    ) -> Cube:
        """
        Method to run the recursive filter.

        Args:
            cube:
                2D cube containing the input data to which the recursive
                filter will be applied.
            smoothing_coefficients_x:
                2D cube containing array of smoothing_coefficient values that
                will be used when applying the recursive filter along the
                x-axis.
            smoothing_coefficients_y:
                2D cube containing array of smoothing_coefficient values that
                will be used when applying the recursive filter along the
                y-axis.
            iterations:
                The number of iterations of the recursive filter

        Returns:
            Cube containing the smoothed field after the recursive filter
            method has been applied to the input cube.
        """
        (x_index, ) = cube.coord_dims(cube.coord(axis="x").name())
        (y_index, ) = cube.coord_dims(cube.coord(axis="y").name())
        output = cube.data

        for _ in range(iterations):
            output = RecursiveFilter._recurse_forward(
                output, smoothing_coefficients_x.data, x_index)
            output = RecursiveFilter._recurse_backward(
                output, smoothing_coefficients_x.data, x_index)
            output = RecursiveFilter._recurse_forward(
                output, smoothing_coefficients_y.data, y_index)
            output = RecursiveFilter._recurse_backward(
                output, smoothing_coefficients_y.data, y_index)
            cube.data = output
        return cube
Esempio n. 8
0
def _math_op_common(
    cube,
    operation_function,
    new_unit,
    new_dtype=None,
    in_place=False,
    skeleton_cube=False,
):
    from iris.cube import Cube

    _assert_is_cube(cube)

    if in_place and not skeleton_cube:
        if cube.has_lazy_data():
            cube.data = operation_function(cube.lazy_data())
        else:
            try:
                operation_function(cube.data, out=cube.data)
            except TypeError:
                # Non-ufunc function
                operation_function(cube.data)
        new_cube = cube
    else:
        data = operation_function(cube.core_data())
        if skeleton_cube:
            # Simply wrap the resultant data in a cube, as no
            # cube metadata is required by the caller.
            new_cube = Cube(data)
        else:
            new_cube = cube.copy(data)

    # If the result of the operation is scalar and masked, we need to fix-up the dtype.
    if (
        new_dtype is not None
        and not new_cube.has_lazy_data()
        and new_cube.data.shape == ()
        and ma.is_masked(new_cube.data)
    ):
        new_cube.data = ma.masked_array(0, 1, dtype=new_dtype)

    _sanitise_metadata(new_cube, new_unit)

    return new_cube
Esempio n. 9
0
    def _standardise_dtypes_and_units(cube: Cube) -> None:
        """
        Modify input cube in place to conform to mandatory dtype and unit
        standards.

        Args:
            cube:
                Cube to be updated in place
        """
        def as_correct_dtype(obj: ndarray, required_dtype: dtype) -> ndarray:
            """
            Returns an object updated if necessary to the required dtype

            Args:
                obj:
                    The object to be updated
                required_dtype:
                    The dtype required

            Returns:
                The updated object
            """
            if obj.dtype != required_dtype:
                return obj.astype(required_dtype)
            return obj

        cube.data = as_correct_dtype(cube.data, get_required_dtype(cube))
        for coord in cube.coords():
            if coord.name() in TIME_COORDS and not check_units(coord):
                coord.convert_units(get_required_units(coord))
            req_dtype = get_required_dtype(coord)
            # ensure points and bounds have the same dtype
            if np.issubdtype(req_dtype, np.integer):
                coord.points = round_close(coord.points)
            coord.points = as_correct_dtype(coord.points, req_dtype)
            if coord.has_bounds():
                if np.issubdtype(req_dtype, np.integer):
                    coord.bounds = round_close(coord.bounds)
                coord.bounds = as_correct_dtype(coord.bounds, req_dtype)
Esempio n. 10
0
    def apply_circular_kernel(self, cube: Cube, ranges: int) -> Cube:
        """
        Method to apply a circular kernel to the data within the input cube in
        order to smooth the resulting field.

        Args:
            cube:
                Cube containing to array to apply CircularNeighbourhood
                processing to.
            ranges:
                Number of grid cells in the x and y direction used to create
                the kernel.

        Returns:
            Cube containing the smoothed field after the kernel has been
            applied.
        """
        data = cube.data
        full_ranges = np.zeros([np.ndim(data)])
        axes = []
        for axis in ["x", "y"]:
            coord_name = cube.coord(axis=axis).name()
            axes.append(cube.coord_dims(coord_name)[0])

        for axis in axes:
            full_ranges[axis] = ranges
        self.kernel = circular_kernel(full_ranges, ranges, self.weighted_mode)
        # Smooth the data by applying the kernel.
        if self.sum_or_fraction == "sum":
            total_area = 1.0
        else:
            # sum_or_fraction is in fraction mode
            total_area = np.sum(self.kernel)

        cube.data = correlate(data, self.kernel, mode="nearest") / total_area
        return cube