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])
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
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
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])
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