Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New callbacks to standardise some dim names #983

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/CSET/operators/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def get_cube_yxcoordname(cube: iris.cube.Cube) -> tuple[str, str]:
Y_COORD_NAMES = ["latitude", "grid_latitude", "projection_y_coordinate", "y"]

# Get a list of coordinate names for the cube
coord_names = [coord.name() for coord in cube.coords()]
coord_names = [coord.name() for coord in cube.dim_coords]

# Check which x-coordinate we have, if any
x_coords = [coord for coord in coord_names if coord in X_COORD_NAMES]
Expand Down
40 changes: 40 additions & 0 deletions src/CSET/operators/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ def callback(cube: iris.cube.Cube, field, filename: str):
_lfric_normalise_callback(cube, field, filename)
_lfric_time_coord_fix_callback(cube, field, filename)
_longitude_fix_callback(cube, field, filename)
_fix_spatialcoord_name_callback(cube)
_fix_pressurecoord_name_callback(cube)

return callback

Expand Down Expand Up @@ -333,6 +335,44 @@ def _longitude_fix_callback(cube: iris.cube.Cube, field, filename):
return cube


def _fix_spatialcoord_name_callback(cube: iris.cube.Cube):
"""Check latitude and longitude coordinates name.

This is necessary as some models define their grid as 'grid_latitude' and 'grid_longitude'
and this means that recipes will fail - particularly if the user is comparing multiple models
where the spatial coordinate names differ.
"""
import CSET.operators._utils as utils

try:
y_name, x_name = utils.get_cube_yxcoordname(cube)
except ValueError:
# Don't modify non-spatial cubes.
return cube

# We only want to modify instances where the coordinate system is actually
# latitude/longitude, and not touch the cube if the coordinate system is say
# meters.
if y_name in ["latitude"] and cube.coord(y_name).units == "degrees":
cube.coord(y_name).rename("grid_latitude")
if x_name in ["longitude"] and cube.coord(x_name).units == "degrees":
cube.coord(x_name).rename("grid_longitude")

return cube


def _fix_pressurecoord_name_callback(cube: iris.cube.Cube):
"""Rename pressure_level coordinate to pressure if it exists."""
# We only want to modify instances where the coordinate system is actually
# latitude/longitude, and not touch the cube if the coordinate system is say
# meters.
for coord in cube.dim_coords:
if coord.name() == "pressure_level":
coord.rename("pressure")

return cube


def _check_input_files(input_path: Path | str, filename_pattern: str) -> Iterable[Path]:
"""Get an iterable of files to load, and check that they all exist.

Expand Down
33 changes: 33 additions & 0 deletions tests/operators/test_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,36 @@ def test_lfric_time_coord_fix_callback_no_time():
cube = iris.cube.Cube([0, 0, 0], aux_coords_and_dims=[(length_coord, 0)])
read._lfric_time_coord_fix_callback(cube, None, None)
assert len(cube.coords("time")) == 0


def test_pressurecoordfix_callback():
"""Check that pressure_level is renamed to pressure if it exists."""
cube = iris.load_cube("tests/test_data/transect_test_umpl.nc")
cube.coord("pressure").rename("pressure_level")
read._fix_pressurecoord_name_callback(cube)
assert (
str(cube.coords)
== "<bound method Cube.coords of <iris 'Cube' of air_temperature / (K) (time: 2; pressure: 16; latitude: 6; longitude: 6)>>"
)


def test_spatialcoordrename_callback():
"""Check that spatial coord gets renamed if it is not grid_latitude."""
# This cube contains 'latitude' and 'longitude'
cube = iris.load_cube("tests/test_data/transect_test_umpl.nc")
read._fix_spatialcoord_name_callback(cube)
assert (
str(cube.coords)
== "<bound method Cube.coords of <iris 'Cube' of air_temperature / (K) (time: 2; pressure: 16; grid_latitude: 6; grid_longitude: 6)>>"
)


def test_spatialcoordnotexist_callback():
"""Check that spatial coord returns cube if cube does not contain spatial coordinates."""
cube = iris.load_cube("tests/test_data/transect_test_umpl.nc")
cube = cube[:, :, 0, 0] # Remove spatial dimcoords
read._fix_spatialcoord_name_callback(cube)
assert (
str(cube.coords)
== "<bound method Cube.coords of <iris 'Cube' of air_temperature / (K) (time: 2; pressure: 16)>>"
)
8 changes: 4 additions & 4 deletions tests/operators/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,23 @@

def test_missing_coord_get_cube_yxcoordname_x(regrid_rectilinear_cube):
"""Missing X coordinate raises error."""
regrid_rectilinear_cube.remove_coord("longitude")
regrid_rectilinear_cube.remove_coord("grid_longitude")
with pytest.raises(ValueError):
operator_utils.get_cube_yxcoordname(regrid_rectilinear_cube)


def test_missing_coord_get_cube_yxcoordname_y(regrid_rectilinear_cube):
"""Missing Y coordinate raises error."""
regrid_rectilinear_cube.remove_coord("longitude")
regrid_rectilinear_cube.remove_coord("grid_longitude")
with pytest.raises(ValueError):
operator_utils.get_cube_yxcoordname(regrid_rectilinear_cube)


def test_get_cube_yxcoordname(regrid_rectilinear_cube):
"""Check that function returns tuple containing horizontal dimension names."""
assert (operator_utils.get_cube_yxcoordname(regrid_rectilinear_cube)) == (
"latitude",
"longitude",
"grid_latitude",
"grid_longitude",
)


Expand Down
Loading