Example #1
0
def process(orographic_enhancement: cli.inputcube,
            *cubes: cli.inputcube,
            attributes_config: cli.inputjson = None,
            ofc_box_size: int = 30,
            smart_smoothing_iterations: int = 100):
    """Calculate optical flow components from input fields.

    Args:
        orographic_enhancement (iris.cube.Cube):
            Cube containing the orographic enhancement fields.
        cubes (iris.cube.CubeList):
            Cubes from which to calculate optical flow velocities.
            These three cubes will be sorted by their time coords.
        attributes_config (dict):
            Dictionary containing required changes to the attributes.
            Every output file will have the attributes_config applied.
        ofc_box_size (int):
            Size of square 'box' (in grid spaces) within which to solve
            the optical flow equations.
        smart_smoothing_iterations (int):
            Number of iterations to perform in enforcing smoothness constraint
            for optical flow velocities.

    Returns:
        iris.cube.CubeList:
            List of the umean and vmean cubes.

    """
    from iris.cube import CubeList

    from improver.nowcasting.optical_flow import \
        generate_optical_flow_components
    from improver.nowcasting.utilities import ApplyOrographicEnhancement

    original_cube_list = CubeList(cubes)
    # order input files by validity time
    original_cube_list.sort(key=lambda x: x.coord("time").points[0])

    # subtract orographic enhancement
    cube_list = ApplyOrographicEnhancement("subtract").process(
            original_cube_list, orographic_enhancement)

    # calculate optical flow velocities from T-1 to T and T-2 to T-1, and
    # average to produce the velocities for use in advection
    u_mean, v_mean = generate_optical_flow_components(
        cube_list, ofc_box_size, smart_smoothing_iterations, attributes_config)

    return CubeList([u_mean, v_mean])
Example #2
0
def generate_optical_flow_components(
        cube_list: CubeList, ofc_box_size: int,
        smart_smoothing_iterations: int) -> Tuple[Cube, Cube]:
    """
    Calculate the mean optical flow components between the cubes in cube_list

    Args:
        cube_list:
            Cubelist from which to calculate optical flow velocities
        ofc_box_size:
            Size of square 'box' (in grid spaces) within which to solve
            the optical flow equations
        smart_smoothing_iterations:
            Number of iterations to perform in enforcing smoothness constraint
            for optical flow velocities

    Returns:
        - Cube of x-advection velocities u_mean
        - Cube of y-advection velocities v_mean
    """
    cube_list.sort(key=lambda x: x.coord("time").points[0])
    time_coord = cube_list[-1].coord("time")

    ofc_plugin = OpticalFlow(iterations=smart_smoothing_iterations)
    u_cubes = iris.cube.CubeList([])
    v_cubes = iris.cube.CubeList([])
    for older_cube, newer_cube in zip(cube_list[:-1], cube_list[1:]):
        ucube, vcube = ofc_plugin(older_cube, newer_cube, boxsize=ofc_box_size)
        u_cubes.append(ucube)
        v_cubes.append(vcube)

    # average optical flow velocity components
    def _calculate_time_average(wind_cubes, time_coord):
        """Average input cubelist over time"""
        cube = wind_cubes.merge_cube()
        try:
            mean = collapsed(cube, "time", iris.analysis.MEAN)
        except CoordinateCollapseError:
            # collapse will fail if there is only one time point
            return cube
        mean.coord("time").points = time_coord.points
        mean.coord("time").units = time_coord.units
        return mean

    u_mean = _calculate_time_average(u_cubes, time_coord)
    v_mean = _calculate_time_average(v_cubes, time_coord)

    return u_mean, v_mean
Example #3
0
def generate_advection_velocities_from_winds(
    cubes: CubeList, background_flow: CubeList, orographic_enhancement: Cube
) -> CubeList:
    """Generate advection velocities as perturbations from a non-zero background
    flow

    Args:
        cubes:
            Two rainfall observations separated by a time difference
        background_flow:
            u- and v-components of a non-zero background flow field
        orographic_enhancement:
            Field containing orographic enhancement data valid for both
            input cube times

    Returns:
        u- and v- advection velocities
    """
    cubes.sort(key=lambda x: x.coord("time").points[0])

    lead_time_seconds = (
        cubes[1].coord("time").cell(0).point - cubes[0].coord("time").cell(0).point
    ).total_seconds()
    lead_time_minutes = int(lead_time_seconds / 60)

    # advect earlier cube forward to match time of later cube, using steering flow
    advected_cube = PystepsExtrapolate(lead_time_minutes, lead_time_minutes)(
        cubes[0], *background_flow, orographic_enhancement
    )[-1]

    # calculate velocity perturbations required to match forecast to observation
    cube_list = ApplyOrographicEnhancement("subtract")(
        [advected_cube, cubes[1]], orographic_enhancement
    )
    perturbations = OpticalFlow(data_smoothing_radius_km=8.0, iterations=20)(
        *cube_list, boxsize=18
    )

    # sum perturbations and original flow field to get advection velocities
    total_advection = _perturb_background_flow(background_flow, perturbations)
    return total_advection
Example #4
0
def process(
    orographic_enhancement: cli.inputcube, *cubes: cli.inputcube,
):
    """Calculate optical flow components from input fields.

    Args:
        orographic_enhancement (iris.cube.Cube):
            Cube containing the orographic enhancement fields.
        cubes (iris.cube.CubeList):
            Cubes from which to calculate optical flow velocities.
            These three cubes will be sorted by their time coords.

    Returns:
        iris.cube.CubeList:
            List of the umean and vmean cubes.

    """
    from iris.cube import CubeList

    from improver.nowcasting.optical_flow import generate_optical_flow_components
    from improver.nowcasting.utilities import ApplyOrographicEnhancement

    original_cube_list = CubeList(cubes)
    # order input files by validity time
    original_cube_list.sort(key=lambda x: x.coord("time").points[0])

    # subtract orographic enhancement
    cube_list = ApplyOrographicEnhancement("subtract")(
        original_cube_list, orographic_enhancement
    )

    # calculate optical flow velocities from T-1 to T and T-2 to T-1, and
    # average to produce the velocities for use in advection
    u_mean, v_mean = generate_optical_flow_components(
        cube_list, ofc_box_size=30, smart_smoothing_iterations=100
    )

    return CubeList([u_mean, v_mean])
Example #5
0
    def _validate_coefficients(self, cube: Cube,
                               smoothing_coefficients: CubeList) -> List[Cube]:
        """Validate the smoothing coefficients cubes.

        Args:
            cube:
                2D 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:
            A list of smoothing coefficients cubes ordered: [x-coeffs, y-coeffs].

        Raises:
            ValueError: Smoothing coefficient cubes are not named correctly.
            ValueError: If any smoothing_coefficient cube value is over 0.5
            ValueError: The coordinate to be smoothed within the
                smoothing coefficient cube is not of the expected length.
            ValueError: The coordinate to be smoothed within the
                smoothing coefficient cube does not have the expected points.
        """
        # Ensure cubes are in x, y order.
        smoothing_coefficients.sort(key=lambda cell: cell.name())
        axes = ["x", "y"]

        for axis, smoothing_coefficient in zip(axes, smoothing_coefficients):

            # Check the smoothing coefficient cube name is as expected
            expected_name = self.smoothing_coefficient_name_format.format(axis)
            if smoothing_coefficient.name() != expected_name:
                msg = (
                    "The smoothing coefficient cube name {} does not match the "
                    "expected name {}".format(smoothing_coefficient.name(),
                                              expected_name))
                raise ValueError(msg)

            # Check the smoothing coefficients do not exceed an empirically determined
            # maximum value; larger values damage conservation significantly.
            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")

            for test_axis in axes:
                coefficient_crd = smoothing_coefficient.coord(axis=test_axis)
                if test_axis == axis:
                    expected_points = (
                        cube.coord(axis=test_axis).points[1:] +
                        cube.coord(axis=test_axis).points[:-1]) / 2
                else:
                    expected_points = cube.coord(axis=test_axis).points

                if len(coefficient_crd.points) != len(
                        expected_points) or not np.allclose(
                            coefficient_crd.points, expected_points):
                    msg = (
                        f"The smoothing coefficients {test_axis} dimension does not "
                        "have the expected length or values compared with the cube "
                        "to which smoothing is being applied.\n\nSmoothing "
                        "coefficient cubes must have coordinates that are:\n"
                        "- one element shorter along the dimension being smoothed "
                        f"({axis}) than in the target cube, with points in that "
                        "dimension equal to the mean of each pair of points along "
                        "the dimension in the target cube\n- equal to the points "
                        "in the target cube along the dimension not being smoothed"
                    )
                    raise ValueError(msg)

        return smoothing_coefficients