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