Example #1
0
 def setUp(self):
     """Set up input cube"""
     self.cube = set_up_variable_cube(
         282 * np.ones((5, 5), dtype=np.float32),
         spatial_grid="latlon",
         standard_grid_metadata="gl_det",
         time=datetime(2019, 10, 11),
         time_bounds=[datetime(2019, 10, 10, 23), datetime(2019, 10, 11)],
         frt=datetime(2019, 10, 10, 18),
     )
     self.plugin = StandardiseMetadata()
Example #2
0
def process(
    cube: cli.inputcube,
    *,
    attributes_config: cli.inputjson = None,
    coords_to_remove: cli.comma_separated_list = None,
    new_name: str = None,
    new_units: str = None,
):
    """
    Standardise a source cube. Available options are renaming, converting units,
    updating attributes and removing named scalar coordinates. Remaining scalar
    coordinates are collapsed, and data are cast to IMPROVER standard datatypes
    and units.

    Deprecated behaviour:
    Translates metadata relating to the grid_id attribute from StaGE
    version 1.1.0 to StaGE version 1.2.0. Cubes that have no "grid_id"
    attribute are not recognised as v1.1.0 and are not changed.

    Args:
        cube (iris.cube.Cube):
            Source cube to be standardised
        attributes_config (dict):
            Dictionary containing required changes that will be applied to
            the attributes.
        coords_to_remove (list):
            List of names of scalar coordinates to remove.
        new_name (str):
            Name of output cube.
        new_units (str):
            Units to convert to.

    Returns:
        iris.cube.Cube
    """
    from improver.metadata.amend import update_stage_v110_metadata
    from improver.standardise import StandardiseMetadata

    # update_stage_v110_metadata is deprecated. Please ensure metadata is
    # StaGE version 1.2.0 compatible.
    update_stage_v110_metadata(cube)

    return StandardiseMetadata()(
        cube,
        new_name=new_name,
        new_units=new_units,
        coords_to_remove=coords_to_remove,
        attributes_dict=attributes_config,
    )
class Test_process(IrisTest):
    """Test the process method"""
    def setUp(self):
        """Set up input cube"""
        self.cube = set_up_variable_cube(
            282 * np.ones((5, 5), dtype=np.float32),
            spatial_grid="latlon",
            standard_grid_metadata="gl_det",
            time=datetime(2019, 10, 11),
            time_bounds=[datetime(2019, 10, 10, 23),
                         datetime(2019, 10, 11)],
            frt=datetime(2019, 10, 10, 18),
        )
        self.plugin = StandardiseMetadata()

    def test_null(self):
        """Test process method with default arguments returns an unchanged
        cube"""
        result = self.plugin.process(self.cube.copy())
        self.assertIsInstance(result, iris.cube.Cube)
        self.assertArrayAlmostEqual(result.data, self.cube.data)
        self.assertEqual(result.metadata, self.cube.metadata)

    def test_standardise_time_coords(self):
        """Test incorrect time-type coordinates are cast to the correct
        datatypes and units"""
        for coord in ["time", "forecast_period"]:
            self.cube.coord(coord).points = self.cube.coord(
                coord).points.astype(np.float64)
            self.cube.coord(coord).bounds = self.cube.coord(
                coord).bounds.astype(np.float64)
        self.cube.coord("forecast_period").convert_units("hours")
        result = self.plugin.process(self.cube)
        self.assertEqual(result.coord("forecast_period").units, "seconds")
        self.assertEqual(
            result.coord("forecast_period").points.dtype, np.int32)
        self.assertEqual(
            result.coord("forecast_period").bounds.dtype, np.int32)
        self.assertEqual(result.coord("time").points.dtype, np.int64)
        self.assertEqual(result.coord("time").bounds.dtype, np.int64)

    def test_standardise_time_coords_missing_fp(self):
        """Test a missing time-type coordinate does not cause an error when
        standardisation is required"""
        self.cube.coord("time").points = self.cube.coord("time").points.astype(
            np.float64)
        self.cube.remove_coord("forecast_period")
        result = self.plugin.process(self.cube)
        self.assertEqual(result.coord("time").points.dtype, np.int64)

    def test_collapse_scalar_dimensions(self):
        """Test scalar dimension is collapsed"""
        cube = iris.util.new_axis(self.cube, "time")
        result = self.plugin.process(cube)
        dim_coord_names = [
            coord.name() for coord in result.coords(dim_coords=True)
        ]
        aux_coord_names = [
            coord.name() for coord in result.coords(dim_coords=False)
        ]
        self.assertSequenceEqual(result.shape, (5, 5))
        self.assertNotIn("time", dim_coord_names)
        self.assertIn("time", aux_coord_names)

    def test_realization_not_collapsed(self):
        """Test scalar realization coordinate is preserved"""
        realization = AuxCoord([1], "realization")
        self.cube.add_aux_coord(realization)
        cube = iris.util.new_axis(self.cube, "realization")
        result = self.plugin.process(cube)
        dim_coord_names = [
            coord.name() for coord in result.coords(dim_coords=True)
        ]
        self.assertSequenceEqual(result.shape, (1, 5, 5))
        self.assertIn("realization", dim_coord_names)

    def test_metadata_changes(self):
        """Test changes to cube name, coordinates and attributes without
        regridding"""
        new_name = "regridded_air_temperature"
        attribute_changes = {
            "institution": "Met Office",
            "mosg__grid_version": "remove",
        }
        expected_attributes = {
            "mosg__grid_domain": "global",
            "mosg__grid_type": "standard",
            "mosg__model_configuration": "gl_det",
            "institution": "Met Office",
        }
        expected_data = self.cube.data.copy() - 273.15
        result = self.plugin.process(
            self.cube,
            new_name=new_name,
            new_units="degC",
            coords_to_remove=["forecast_period"],
            attributes_dict=attribute_changes,
        )
        self.assertEqual(result.name(), new_name)
        self.assertEqual(result.units, "degC")
        self.assertArrayAlmostEqual(result.data, expected_data, decimal=5)
        self.assertDictEqual(result.attributes, expected_attributes)
        self.assertNotIn("forecast_period",
                         [coord.name() for coord in result.coords()])

    def test_float_deescalation(self):
        """Test precision de-escalation from float64 to float32"""
        cube = self.cube.copy()
        cube.data = cube.data.astype(np.float64)
        result = self.plugin.process(cube)
        self.assertEqual(result.data.dtype, np.float32)
        self.assertArrayAlmostEqual(result.data, self.cube.data, decimal=4)

    def test_float_deescalation_with_unit_change(self):
        """Covers the bug where unit conversion from an integer input field causes
        float64 escalation"""
        cube = set_up_variable_cube(np.ones((5, 5), dtype=np.int16),
                                    name="rainrate",
                                    units="mm h-1")
        result = self.plugin.process(cube, new_units="m s-1")
        self.assertEqual(cube.dtype, np.float32)
        self.assertEqual(result.data.dtype, np.float32)