From 19d41b9db00acdec6c5dfb16d92ee57d3ae4d454 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 23 Mar 2026 09:39:18 +0000 Subject: [PATCH 01/17] add cbar entry temp --- src/CSET/operators/_colorbar_definition.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/CSET/operators/_colorbar_definition.json b/src/CSET/operators/_colorbar_definition.json index b1cc2a0ec..ebee01403 100644 --- a/src/CSET/operators/_colorbar_definition.json +++ b/src/CSET/operators/_colorbar_definition.json @@ -306,6 +306,11 @@ "max": 1, "min": -1 }, + "y_wind": { + "cmap": "RdBu_r", + "max": -30, + "min": 30 + }, "meridional_wind_at_pressure_levels": { "cmap": "RdBu_r", "max": 40, From 4da271de27b6a13a5604321f33a3d6d35eef1e3e Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 23 Mar 2026 09:39:49 +0000 Subject: [PATCH 02/17] transect inset --- src/CSET/operators/plot.py | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/CSET/operators/plot.py b/src/CSET/operators/plot.py index 208750607..713089ff3 100644 --- a/src/CSET/operators/plot.py +++ b/src/CSET/operators/plot.py @@ -491,6 +491,7 @@ def _plot_and_save_spatial_plot( # Plot the field. if method == "contourf": # Filled contour plot of the field. + logging.info("testing!") plot = iplt.contourf(cube, cmap=cmap, levels=levels, norm=norm) elif method == "pcolormesh": try: @@ -530,6 +531,63 @@ def _plot_and_save_spatial_plot( fontsize=16, ) + # Inset code + import cartopy.feature as cfeature + from cartopy.mpl.geoaxes import GeoAxes + from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + axins = inset_axes( + axes, + width="20%", + height="20%", + loc="upper right", + axes_class=GeoAxes, + axes_kwargs=dict(map_projection=ccrs.PlateCarree()), + ) + + axins.coastlines(resolution="50m") + axins.add_feature(cfeature.BORDERS, linewidth=0.3) + + SLat = float(cube.attributes["transect_coords"].split("_")[0]) + SLon = float(cube.attributes["transect_coords"].split("_")[1]) + ELat = float(cube.attributes["transect_coords"].split("_")[2]) + ELon = float(cube.attributes["transect_coords"].split("_")[3]) + + # Plot points (note: lon, lat order for Cartopy) + axins.plot(SLon, SLat, marker="x", color="green", transform=ccrs.PlateCarree()) + axins.plot(ELon, ELat, marker="x", color="red", transform=ccrs.PlateCarree()) + + # Draw line between them + axins.plot( + [SLon, ELon], [SLat, ELat], color="black", transform=ccrs.PlateCarree() + ) + + lon_min, lon_max = sorted([SLon, ELon]) + lat_min, lat_max = sorted([SLat, ELat]) + + # Midpoints + lon_mid = (lon_min + lon_max) / 2 + lat_mid = (lat_min + lat_max) / 2 + + # Maximum half-range + half_range = max(lon_max - lon_min, lat_max - lat_min) / 2 + if half_range == 0: # points identical → provide small default + half_range = 1 + + # Set square extent + axins.set_extent( + [ + lon_mid - half_range, + lon_mid + half_range, + lat_mid - half_range, + lat_mid + half_range, + ], + crs=ccrs.PlateCarree(), + ) + + # Ensure square aspect + axins.set_aspect("equal") + else: # Add title. axes.set_title(title, fontsize=16) From 26770584d0b16c87290a887f37db781b7e8cf024 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 23 Mar 2026 09:40:22 +0000 Subject: [PATCH 03/17] add subarea --- src/CSET/recipes/level_fields/transect.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CSET/recipes/level_fields/transect.yaml b/src/CSET/recipes/level_fields/transect.yaml index 0da565aa2..f11ac0138 100644 --- a/src/CSET/recipes/level_fields/transect.yaml +++ b/src/CSET/recipes/level_fields/transect.yaml @@ -22,6 +22,8 @@ steps: operator: constraints.generate_level_constraint coordinate: $VERTICAL_COORDINATE levels: '*' + subarea_type: $SUBAREA_TYPE + subarea_extent: $SUBAREA_EXTENT - operator: transect.calc_transect startcoords: $START_COORDS From c66551b17ab192ceea54ccdf5edc4683e716e4e8 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 23 Mar 2026 09:42:28 +0000 Subject: [PATCH 04/17] tmp --- ...ators_colorbar_definition.json-sorted.json | 934 ++++++++++++++++++ 1 file changed, 934 insertions(+) create mode 100644 .tmp-srcCSEToperators_colorbar_definition.json-sorted.json diff --git a/.tmp-srcCSEToperators_colorbar_definition.json-sorted.json b/.tmp-srcCSEToperators_colorbar_definition.json-sorted.json new file mode 100644 index 000000000..d675b1857 --- /dev/null +++ b/.tmp-srcCSEToperators_colorbar_definition.json-sorted.json @@ -0,0 +1,934 @@ +{ + "air_potential_temperature": { + "cmap": "RdYlBu_r", + "max": 350, + "min": 280, + "ymax": "auto", + "ymin": "auto" + }, + "air_pressure_at_mean_sea_level": { + "cmap": "coolwarm", + "max": 104000, + "min": 94000, + "ymax": "auto", + "ymin": "auto" + }, + "air_pressure_at_mean_sea_level_difference": { + "cmap": "bwr", + "max": 1000, + "min": -1000 + }, + "atmosphere_boundary_layer_thickness": { + "cmap": "terrain", + "max": 5000, + "min": 0, + "ymax": 2000, + "ymin": 0 + }, + "atmosphere_boundary_layer_thickness_difference": { + "cmap": "PRGn", + "max": 1000.0, + "min": -1000.0 + }, + "atmosphere_mass_content_of_cloud_ice": { + "cmap": "Blues", + "max": 5, + "min": 0.0 + }, + "atmosphere_mass_content_of_cloud_ice_difference": { + "cmap": "bwr", + "max": 5, + "min": -5 + }, + "atmosphere_mass_content_of_cloud_liquid_water": { + "cmap": "Blues", + "max": 5, + "min": 0.0, + "ymax": 1.0, + "ymin": 0.0 + }, + "atmosphere_mass_content_of_cloud_liquid_water_difference": { + "cmap": "bwr", + "max": 5, + "min": -5 + }, + "atmosphere_mass_content_of_water_vapor": { + "cmap": "Blues", + "max": 50, + "min": 0 + }, + "atmosphere_mass_content_of_water_vapor_difference": { + "cmap": "bwr", + "max": 50, + "min": -50 + }, + "atmosphere_mass_of_air_per_unit_area": { + "cmap": "Greys", + "max": 10560, + "min": 9200, + "ymax": 10200, + "ymin": 10000 + }, + "atmosphere_mass_of_air_per_unit_area_difference": { + "cmap": "bwr", + "max": 500, + "min": -500 + }, + "ceilometer_filtered_combined_cloud_amount_maximum_random_overlap": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "ceilometer_filtered_combined_cloud_amount_maximum_random_overlap_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "cloud_area_fraction": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "cloud_area_fraction_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "cloud_base_altitude": { + "cmap": "terrain", + "max": 25, + "min": 0 + }, + "cloud_base_altitude_difference": { + "cmap": "PRGn", + "max": 20.0, + "min": -20.0 + }, + "cloud_ice_mixing_ratio": { + "cmap": "GnBu", + "max": 1, + "min": 0.0001 + }, + "cloud_liquid_water_mixing_ratio": { + "camp": "GnBu", + "max": 1, + "min": 0.0001 + }, + "combined_cloud_amount": { + "cmap": "Greys_r", + "max": 1, + "min": 0 + }, + "combined_cloud_amount_maximum_random_overlap": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "combined_cloud_amount_maximum_random_overlap_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "dew_point_temperature_at_screen_level": { + "cmap": "RdYlBu_r", + "max": 315, + "min": 263, + "ymax": "auto", + "ymin": "auto" + }, + "dew_point_temperature_at_screen_level_difference": { + "cmap": "bwr", + "max": 10, + "min": -10 + }, + "eastward_wind_at_10m": { + "cmap": "RdBu_r", + "max": 25.0, + "min": -25.0, + "ymax": 8.0, + "ymin": -8.0 + }, + "eastward_wind_at_10m_difference": { + "cmap": "bwr", + "max": 5, + "min": -5 + }, + "eastward_wind_northward_wind_magnitude": { + "cmap": "YlOrRd", + "max": 25.0, + "min": 0.0 + }, + "exner_pressure_at_cell_interfaces": { + "cmap": "coolwarm", + "max": 1.1, + "min": 0.5 + }, + "fog_fraction_at_screen_level": { + "cmap": "viridis", + "max": 1, + "min": 0, + "ymax": 0.5, + "ymin": 0.0 + }, + "fog_fraction_at_screen_level_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "geopotential_height_at_pressure_levels": { + "cmap": "bwr", + "max": 15000, + "min": -250, + "pressure_levels": { + "1000": { + "max": 250, + "min": -250, + "ymax": "auto", + "ymin": "auto" + }, + "250": { + "max": 10800, + "min": 10200, + "ymax": "auto", + "ymin": "auto" + }, + "500": { + "max": 6000, + "min": 5000, + "ymax": "auto", + "ymin": "auto" + }, + "850": { + "max": 1700, + "min": 1200, + "ymax": "auto", + "ymin": "auto" + } + }, + "ymax": "auto", + "ymin": "auto" + }, + "geopotential_height_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 60, + "min": -60 + }, + "grid_surface_snow_amount": { + "cmap": "cool", + "max": 1, + "min": 0, + "ymax": 0.1, + "ymin": 0.0 + }, + "grid_surface_snow_amount_difference": { + "cmap": "BrBG", + "max": 1, + "min": -1 + }, + "grid_surface_temperature": { + "cmap": "RdYlBu_r", + "max": 323, + "min": 263, + "ymax": "auto", + "ymin": "auto" + }, + "grid_surface_temperature_difference": { + "cmap": "bwr", + "max": -10, + "min": 10 + }, + "grid_surface_upward_latent_heat_flux": { + "cmap": "RdBu_r", + "max": 500, + "min": -500, + "ymax": 250, + "ymin": -50 + }, + "grid_surface_upward_latent_heat_flux_difference": { + "cmap": "PuOr_r", + "max": 80, + "min": -80 + }, + "grid_surface_upward_sensible_heat_flux": { + "cmap": "bwr", + "max": 250.0, + "min": -250.0, + "ymax": 250.0, + "ymin": -50.0 + }, + "grid_surface_upward_sensible_heat_flux_difference": { + "cmap": "PuOr", + "max": 80.0, + "min": -80.0 + }, + "high_type_cloud_area_fraction": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "high_type_cloud_area_fraction_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "low_type_cloud_area_fraction": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "low_type_cloud_area_fraction_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "mass_content_of_water_in_soil_layer": { + "cmap": "ocean_r", + "max": 500, + "min": -10 + }, + "maximum_combined_cloud_amount_below_111m_asl": { + "cmap": "viridis", + "max": 1.0, + "min": 0.0 + }, + "maximum_combined_cloud_amount_below_111m_asl_difference": { + "cmap": "PRGn", + "max": 1.0, + "min": -1.0 + }, + "medium_type_cloud_area_fraction": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "medium_type_cloud_area_fraction_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "meridional_wind_at_pressure_levels": { + "cmap": "RdBu_r", + "max": 40, + "min": -40, + "pressure_levels": { + "100": { + "max": 100, + "min": -100, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "1000": { + "max": 35, + "min": -35, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "200": { + "max": 80, + "min": -80, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "250": { + "max": 50, + "min": -50, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "300": { + "max": 55, + "min": -55, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "500": { + "max": 40, + "min": -40, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "700": { + "max": 35, + "min": -35, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "850": { + "max": 35, + "min": -35, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "950": { + "max": 35, + "min": -35, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + } + }, + "xmax": 20, + "xmin": -20 + }, + "meridional_wind_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 20, + "min": -20 + }, + "northward_wind_at_10m": { + "cmap": "RdBu_r", + "max": 25.0, + "min": -25.0, + "ymax": 8.0, + "ymin": -8.0 + }, + "northward_wind_at_10m_difference": { + "cmap": "bwr", + "max": 5.0, + "min": -5.0 + }, + "northward_wind_at_cell_centres": { + "cmap": "RdBu_r", + "max": 35.0, + "min": -35.0 + }, + "number_of_lightning_flashes_in_column": { + "cmap": "YlOrRd", + "max": 5, + "min": 1, + "ymax": 0.5, + "ymin": 0 + }, + "number_of_lightning_flashes_in_column_difference": { + "cmap": "RdGy_r", + "max": 5, + "min": -5 + }, + "potential_vorticity_at_pressure_levels": { + "cmap": "RdBu_r", + "max": 1.8e-05, + "min": -1.8e-05, + "ymax": 5e-06, + "ymin": -5e-06 + }, + "potential_vorticity_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 1e-05, + "min": -1e-05 + }, + "radar_reflectivity_at_1km_above_the_surface": { + "cmap": "cubehelix_r", + "max": 70.0, + "min": -35.0 + }, + "radar_reflectivity_at_1km_above_the_surface_difference": { + "cmap": "BrBG", + "max": 100.0, + "min": -100.0 + }, + "relative_humidity_at_screen_level": { + "cmap": "Blues", + "max": 110, + "min": 0 + }, + "relative_humidity_at_screen_level_difference": { + "cmap": "BrBG", + "max": 30, + "min": -30 + }, + "relative_humidity_wrt_ice_at_pressure_levels": { + "cmap": "Blues", + "max": 110, + "min": 0 + }, + "relative_humidity_wrt_ice_at_pressure_levels_difference": { + "cmap": "BrBG", + "max": 50, + "min": -50 + }, + "soil_temperature": { + "cmap": "RdYlBu_r", + "max": 323, + "min": 263, + "ymax": "auto", + "ymin": "auto" + }, + "soil_temperature_difference": { + "cmap": "bwr", + "max": 0.5, + "min": -0.5 + }, + "structural_similarity": { + "cmap": "afmhot", + "max": 1.0, + "min": -1.0 + }, + "surface_downward_longwave_flux": { + "cmap": "Greys_r", + "max": 500, + "min": 0 + }, + "surface_downward_longwave_flux_difference": { + "cmap": "PuOr", + "max": 500, + "min": -500 + }, + "surface_downward_shortwave_flux": { + "cmap": "Greys", + "max": 1400, + "min": 0, + "ymax": 800, + "ymin": 0 + }, + "surface_downward_shortwave_flux_difference": { + "cmap": "PuOr", + "max": 1400, + "min": -1400 + }, + "surface_microphysical_rainfall_amount": { + "cmap": "cividis", + "max": 256, + "min": 0, + "ymax": 1.0, + "ymin": 0.0 + }, + "surface_microphysical_rainfall_amount_difference": { + "cmap": "BrBG", + "max": 16.0, + "min": -16.0 + }, + "surface_microphysical_rainfall_rate": { + "cmap": "cividis", + "levels": [ + 0, + 0.125, + 0.25, + 0.5, + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256 + ], + "ymax": 1.0, + "ymin": 0.0 + }, + "surface_microphysical_rainfall_rate_difference": { + "cmap": "BrBG", + "max": 32.0, + "min": -32.0 + }, + "surface_microphysical_snowfall_amount": { + "cmap": "cividis", + "max": 256, + "min": 0, + "ymax": 1.0, + "ymin": 0.0 + }, + "surface_microphysical_snowfall_amount_difference": { + "cmap": "BrBG", + "max": 4.0, + "min": -4.0 + }, + "surface_microphysical_snowfall_rate": { + "cmap": "cool", + "levels": [ + 0, + 0.125, + 0.25, + 0.5, + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256 + ], + "ymax": 1.0, + "ymin": 0.0 + }, + "surface_microphysical_snowfall_rate_difference": { + "cmap": "BrBG", + "max": 8.0, + "min": -8.0 + }, + "surface_net_longwave_flux_radiative_timestep": { + "cmap": "bwr", + "max": 250, + "min": -250, + "ymax": 0, + "ymin": -150 + }, + "surface_net_longwave_flux_radiative_timestep_difference": { + "cmap": "PuOr", + "max": 80, + "min": -80 + }, + "surface_net_shortwave_flux": { + "cmap": "bwr", + "max": 1400, + "min": -1400, + "ymax": 850, + "ymin": 0 + }, + "surface_net_shortwave_flux_difference": { + "cmap": "PuOr", + "max": 1400, + "min": -1400 + }, + "temperature_at_pressure_levels": { + "cmap": "RdYlBu_r", + "max": 320, + "min": 240, + "pressure_levels": { + "100": { + "max": 240, + "min": 200, + "ymax": "auto", + "ymin": "auto" + }, + "1000": { + "max": 320, + "min": 240, + "ymax": "auto", + "ymin": "auto" + }, + "200": { + "max": 240, + "min": 200, + "ymax": "auto", + "ymin": "auto" + }, + "250": { + "max": 240, + "min": 200, + "ymax": "auto", + "ymin": "auto" + }, + "300": { + "max": 250, + "min": 200, + "ymax": "auto", + "ymin": "auto" + }, + "500": { + "max": 280, + "min": 220, + "ymax": "auto", + "ymin": "auto" + }, + "700": { + "max": 300, + "min": 240, + "ymax": "auto", + "ymin": "auto" + }, + "850": { + "max": 310, + "min": 250, + "ymax": "auto", + "ymin": "auto" + }, + "950": { + "max": 310, + "min": 250, + "ymax": "auto", + "ymin": "auto" + } + }, + "ymax": "auto", + "ymin": "auto" + }, + "temperature_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 10, + "min": -10 + }, + "temperature_at_screen_level": { + "cmap": "RdYlBu_r", + "max": 323, + "min": 263, + "ymax": "auto", + "ymin": "auto" + }, + "temperature_at_screen_level_difference": { + "cmap": "bwr", + "max": 10, + "min": -10 + }, + "toa_direct_shortwave_flux": { + "cmap": "Greys", + "max": 1400.0, + "min": 0.0 + }, + "toa_direct_shortwave_flux_difference": { + "cmap": "PuOr", + "max": 2.0, + "min": -2.0 + }, + "toa_upward_longwave_flux_radiative_timestep": { + "cmap": "Greys", + "max": 350.0, + "min": 0.0, + "ymax": 350.0, + "ymin": 150.0 + }, + "toa_upward_longwave_flux_radiative_timestep_difference": { + "cmap": "PuOr", + "max": 100.0, + "min": -100.0 + }, + "toa_upward_shortwave_flux": { + "cmap": "Greys", + "max": 500.0, + "min": 0.0 + }, + "toa_upward_shortwave_flux_difference": { + "cmap": "PuOr", + "max": 100, + "min": -100 + }, + "turbulent_mixing_height": { + "cmap": "terrain", + "max": 5000, + "min": 0, + "ymax": 2000, + "ymin": 0 + }, + "turbulent_mixing_height_difference": { + "cmap": "PRGn", + "max": 5000, + "min": -5000 + }, + "vapour_specific_humidity_at_pressure_levels_for_climate_averaging": { + "cmap": "Blues", + "max": 0.01, + "min": 0, + "pressure_levels": { + "100": { + "max": 5e-05, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "1000": { + "max": 0.01, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "200": { + "max": 5e-05, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "250": { + "max": 5e-05, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "300": { + "max": 0.0001, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "500": { + "max": 0.003, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "700": { + "max": 0.003, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "850": { + "max": 0.005, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "950": { + "max": 0.01, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + } + }, + "xmax": 0.015, + "xmin": 0.0, + "ymax": "auto", + "ymin": "auto" + }, + "vapour_specific_humidity_at_pressure_levels_for_climate_averaging_difference": { + "cmap": "BrBG", + "max": 0.001, + "min": -0.001 + }, + "vertical_wind_at_pressure_levels": { + "cmap": "RdBu_r", + "max": 1, + "min": -1, + "xmax": 0.08, + "xmin": -0.08, + "ymax": 0.1, + "ymin": -0.1 + }, + "vertical_wind_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 1, + "min": -1 + }, + "visibility_in_air": { + "cmap": "viridis", + "max": 30000.0, + "min": 0.0 + }, + "visibility_in_air_difference": { + "cmap": "PRGn", + "max": 5000.0, + "min": -5000.0 + }, + "wet_bulb_potential_temperature_at_pressure_levels": { + "cmap": "RdYlBu_r", + "max": 340, + "min": 260, + "ymax": "auto", + "ymin": "auto" + }, + "wet_bulb_potential_temperature_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 5, + "min": -5 + }, + "wind_speed_at_10m": { + "cmap": "RdBu_r", + "max": 25, + "min": 0 + }, + "wind_speed_at_10m_difference": { + "cmap": "bwr", + "max": 5, + "min": -5 + }, + "y_wind": { + "cmap": "RdBu_r", + "max": -30, + "min": 30 + }, + "zonal_wind_at_pressure_levels": { + "cmap": "RdBu_r", + "max": 50, + "min": -50, + "pressure_levels": { + "100": { + "max": 100, + "min": -100, + "xmax": 25, + "xmin": -25, + "ymax": 20, + "ymin": -20 + }, + "1000": { + "max": 35, + "min": -35, + "xmax": 25, + "xmin": -25, + "ymax": 20, + "ymin": -20 + }, + "200": { + "max": 80, + "min": -80, + "xmax": 25, + "xmin": -25, + "ymax": 20, + "ymin": -20 + }, + "250": { + "max": 70, + "min": -70, + "xmax": 25, + "xmin": -25, + "ymax": 20, + "ymin": -20 + }, + "300": { + "max": 55, + "min": -55, + "xmax": 25, + "xmin": -25, + "ymax": 20, + "ymin": -20 + }, + "500": { + "max": 45, + "min": -45, + "xmax": 25, + "xmin": -25, + "ymax": 10, + "ymin": -10 + }, + "700": { + "max": 35, + "min": -35, + "xmax": 25, + "xmin": -25, + "ymax": 10, + "ymin": -10 + }, + "850": { + "max": 35, + "min": -35, + "xmax": 25, + "xmin": -25, + "ymax": 10, + "ymin": -10 + }, + "950": { + "max": 35, + "min": -35, + "xmax": 25, + "xmin": -25, + "ymax": 10, + "ymin": -10 + } + }, + "xmax": 25, + "xmin": -25 + }, + "zonal_wind_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 20, + "min": -20 + } +} From 9ae5968040f487ccb3a851a044c106156f421fe1 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 26 Mar 2026 11:12:17 +0000 Subject: [PATCH 05/17] revert mod to colorbar json --- src/CSET/operators/_colorbar_definition.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/CSET/operators/_colorbar_definition.json b/src/CSET/operators/_colorbar_definition.json index ebee01403..b1cc2a0ec 100644 --- a/src/CSET/operators/_colorbar_definition.json +++ b/src/CSET/operators/_colorbar_definition.json @@ -306,11 +306,6 @@ "max": 1, "min": -1 }, - "y_wind": { - "cmap": "RdBu_r", - "max": -30, - "min": 30 - }, "meridional_wind_at_pressure_levels": { "cmap": "RdBu_r", "max": 40, From cad8ae08cc9f5dc56360d444c4a097c5e55721d8 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 26 Mar 2026 12:13:23 +0000 Subject: [PATCH 06/17] fix varname --- src/CSET/operators/read.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 6c30af1e7..68f160fb5 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -374,6 +374,7 @@ def _loading_callback(cube: iris.cube.Cube, field, filename: str) -> iris.cube.C _proleptic_gregorian_fix(cube) _lfric_time_callback(cube) _lfric_forecast_period_standard_name_callback(cube) + _normalise_ML_varname(cube) return cube @@ -972,3 +973,14 @@ def _lfric_forecast_period_standard_name_callback(cube: iris.cube.Cube): coord.standard_name = "forecast_period" except iris.exceptions.CoordinateNotFoundError: pass + + +def _normalise_ML_varname(cube: iris.cube.Cube): + """Fix variable names in ML models to standard names.""" + if cube.coords("pressure"): + if cube.name() == "x_wind": + cube.long_name = "zonal_wind_at_pressure_levels" + if cube.name() == "y_wind": + cube.long_name = "meridional_wind_at_pressure_levels" + if cube.name() == "air_temperature": + cube.long_name = "temperature_at_pressure_levels" From 29d2deaf8d5d394b5ec92ad21a3d2b7270095d32 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 26 Mar 2026 14:24:53 +0000 Subject: [PATCH 07/17] new aggregation recipe --- ...transect_case_aggregation_hour_of_day.yaml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml diff --git a/src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml b/src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml new file mode 100644 index 000000000..c1760e83f --- /dev/null +++ b/src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml @@ -0,0 +1,40 @@ +category: Transect Aggregation +title: $MODEL_NAME Transect of $VARNAME +description: | + Extracts a $VERTICAL_COORDINATE transect for $VARNAME between two points and + plots it. + + Start coordinate: `$START_COORDS` + End coordinate: `$FINISH_COORDS` + +steps: + - operator: read.read_cube + file_paths: $INPUT_PATHS + constraint: + operator: constraints.combine_constraints + cell_method_constraint: + operator: constraints.generate_cell_methods_constraint + cell_methods: [] + var_constraint: + operator: constraints.generate_var_constraint + varname: $VARNAME + level_constraint: + operator: constraints.generate_level_constraint + coordinate: $VERTICAL_COORDINATE + levels: '*' + subarea_type: $SUBAREA_TYPE + subarea_extent: $SUBAREA_EXTENT + + - operator: aggregate.ensure_aggregatable_across_cases + + - operator: collapse.collapse_by_hour_of_day + method: MEAN + + - operator: transect.calc_transect + startcoords: $START_COORDS + endcoords: $FINISH_COORDS + + - operator: plot.spatial_contour_plot + + - operator: write.write_cube_to_nc + overwrite: True From b390fbb3c95db4cd6fadc4d5095fe6d96ad7bc70 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 26 Mar 2026 14:25:27 +0000 Subject: [PATCH 08/17] adjust padding for transects --- src/CSET/operators/plot.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/CSET/operators/plot.py b/src/CSET/operators/plot.py index 713089ff3..803018f67 100644 --- a/src/CSET/operators/plot.py +++ b/src/CSET/operators/plot.py @@ -592,11 +592,19 @@ def _plot_and_save_spatial_plot( # Add title. axes.set_title(title, fontsize=16) + # Adjust padding if spatial plot or transect + if is_transect(cube): + yinfopad = -0.1 + ycbarpad = 0.1 + else: + yinfopad = -0.05 + ycbarpad = 0.042 + # Add watermark with min/max/mean. Currently not user togglable. # In the bbox dictionary, fc and ec are hex colour codes for grey shade. axes.annotate( f"Min: {np.min(cube.data):.3g} Max: {np.max(cube.data):.3g} Mean: {np.mean(cube.data):.3g}", - xy=(1, -0.05), + xy=(1, yinfopad), xycoords="axes fraction", xytext=(-5, 5), textcoords="offset points", @@ -607,7 +615,7 @@ def _plot_and_save_spatial_plot( ) # Add colour bar. - cbar = fig.colorbar(plot, orientation="horizontal", pad=0.042, shrink=0.7) + cbar = fig.colorbar(plot, orientation="horizontal", pad=ycbarpad, shrink=0.7) cbar.set_label(label=f"{cube.name()} ({cube.units})", size=14) # add ticks and tick_labels for every levels if less than 20 levels exist if levels is not None and len(levels) < 20: From 0955f44dfd602785fdfe549a4c172668ac2d25e9 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 26 Mar 2026 14:26:07 +0000 Subject: [PATCH 09/17] add aggregate loader --- src/CSET/loaders/transects.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/CSET/loaders/transects.py b/src/CSET/loaders/transects.py index a69636f71..ed73d50bc 100644 --- a/src/CSET/loaders/transects.py +++ b/src/CSET/loaders/transects.py @@ -47,6 +47,31 @@ def load(conf: Config): aggregation=False, ) + # Create a list of case aggregation types. + AGGREGATION_TYPES = ["lead_time", "hour_of_day", "validity_time", "all"] + + # Transect aggregation + for atype, field in itertools.product( + AGGREGATION_TYPES, conf.PRESSURE_LEVEL_FIELDS + ): + if conf.PLEVEL_TRANSECT_AGGREGATION[AGGREGATION_TYPES.index(atype)]: + yield RawRecipe( + recipe=f"transect_case_aggregation_{atype}.yaml", + variables={ + "VARNAME": field, + "VERTICAL_COORDINATE": "pressure", + "MODEL_NAME": model["name"], + "START_COORDS": conf.PLEVEL_TRANSECT_STARTCOORDS, + "FINISH_COORDS": conf.PLEVEL_TRANSECT_FINISHCOORDS, + "SUBAREA_TYPE": conf.SUBAREA_TYPE if conf.SELECT_SUBAREA else None, + "SUBAREA_EXTENT": conf.SUBAREA_EXTENT + if conf.SELECT_SUBAREA + else None, + }, + model_ids=[model["id"] for model in models], + aggregation=True, + ) + # Model level fields if conf.EXTRACT_MLEVEL_TRANSECT: for model, field in itertools.product( From 3f40aaa84fc23a844fd1cdbcadc64b8a45d6ba0b Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 26 Mar 2026 14:29:31 +0000 Subject: [PATCH 10/17] add gui entries --- .../cset_workflow/meta/diagnostics/rose-meta.conf | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf b/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf index a277c35a1..9a57c9aa3 100644 --- a/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf +++ b/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf @@ -432,6 +432,18 @@ type=real,real compulsory=true sort-key=1pressure8b +[template variables=PLEVEL_TRANSECT_AGGREGATION] +ns=Diagnostics/Pressure +description=Aggregate transects for each time. + Select all options required. + Option1: Aggregate by lead time. + Option2: Aggregate by hour of day. + Option3: Aggregate by validity time. + Option4: All cases aggregated to single profile. +type=python_boolean,python_boolean,python_boolean,python_boolean +compulsory=true +sort-key=1pressure8c + [template variables=SPECTRUM_PLEVEL_FIELD] ns=Diagnostics/Pressure description=Create spectrum of specified pressure level fields. From e1bee533b12b1537cbdeea759889684c5169a609 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 30 Mar 2026 16:39:40 +0100 Subject: [PATCH 11/17] update conf example with aggregate options --- src/CSET/cset_workflow/rose-suite.conf.example | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CSET/cset_workflow/rose-suite.conf.example b/src/CSET/cset_workflow/rose-suite.conf.example index 1ea558e57..b39d1c131 100644 --- a/src/CSET/cset_workflow/rose-suite.conf.example +++ b/src/CSET/cset_workflow/rose-suite.conf.example @@ -88,6 +88,7 @@ MODEL_LEVEL_FIELDS=[] !!MODULES_PURGE=True !!ONE_TO_ONE=False PLACEHOLDER_OBS=False +!!PLEVEL_TRANSECT_AGGREGATION=True,True,True,True !!PLEVEL_TRANSECT_FINISHCOORDS= !!PLEVEL_TRANSECT_STARTCOORDS= PLOTTING_PROJECTION="" From 8ac4040e6a4da7cf9e8abf50c4ac1ee0244eabb9 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 30 Mar 2026 16:40:45 +0100 Subject: [PATCH 12/17] change ordering of logic to prevent multiple models being loaded --- src/CSET/loaders/transects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CSET/loaders/transects.py b/src/CSET/loaders/transects.py index ed73d50bc..55f425040 100644 --- a/src/CSET/loaders/transects.py +++ b/src/CSET/loaders/transects.py @@ -51,8 +51,8 @@ def load(conf: Config): AGGREGATION_TYPES = ["lead_time", "hour_of_day", "validity_time", "all"] # Transect aggregation - for atype, field in itertools.product( - AGGREGATION_TYPES, conf.PRESSURE_LEVEL_FIELDS + for model, atype, field in itertools.product( + models, AGGREGATION_TYPES, conf.PRESSURE_LEVEL_FIELDS ): if conf.PLEVEL_TRANSECT_AGGREGATION[AGGREGATION_TYPES.index(atype)]: yield RawRecipe( @@ -68,7 +68,7 @@ def load(conf: Config): if conf.SELECT_SUBAREA else None, }, - model_ids=[model["id"] for model in models], + model_ids=model["id"], aggregation=True, ) From 924392ed51620718af617e4592752867d4423ed5 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 30 Mar 2026 16:41:51 +0100 Subject: [PATCH 13/17] extra attribute removal and fix forecast period units --- src/CSET/operators/read.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 68f160fb5..989f235b1 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -431,6 +431,9 @@ def _lfric_normalise_callback(cube: iris.cube.Cube, field, filename): cube.attributes.pop("timeStamp", None) cube.attributes.pop("uuid", None) cube.attributes.pop("name", None) + cube.attributes.pop("source", None) + cube.attributes.pop("analysis_source", None) + cube.attributes.pop("history", None) # Sort STASH code list. stash_list = cube.attributes.get("um_stash_source") @@ -969,6 +972,8 @@ def _lfric_forecast_period_standard_name_callback(cube: iris.cube.Cube): """Add forecast_period standard name if missing.""" try: coord = cube.coord("forecast_period") + if coord.units != "hours": + cube.coord("forecast_period").convert_units("hours") if not coord.standard_name: coord.standard_name = "forecast_period" except iris.exceptions.CoordinateNotFoundError: From 9dd2e54f412a03f7a371da7c2e40c16ae21f1a09 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 30 Mar 2026 16:42:46 +0100 Subject: [PATCH 14/17] switch to using read_cubes, refine seq coord for plotting and title --- .../transect_case_aggregation_hour_of_day.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml b/src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml index c1760e83f..7acc8a9b4 100644 --- a/src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml +++ b/src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml @@ -1,5 +1,5 @@ -category: Transect Aggregation -title: $MODEL_NAME Transect of $VARNAME +category: Transect +title: $MODEL_NAME Transect of $VARNAME Aggregation by hour of day. description: | Extracts a $VERTICAL_COORDINATE transect for $VARNAME between two points and plots it. @@ -8,7 +8,7 @@ description: | End coordinate: `$FINISH_COORDS` steps: - - operator: read.read_cube + - operator: read.read_cubes file_paths: $INPUT_PATHS constraint: operator: constraints.combine_constraints @@ -35,6 +35,7 @@ steps: endcoords: $FINISH_COORDS - operator: plot.spatial_contour_plot + sequence_coordinate: hour - operator: write.write_cube_to_nc overwrite: True From 8d57dfc2349c772c5a6c87fef6372a8a81111f76 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 30 Mar 2026 16:43:10 +0100 Subject: [PATCH 15/17] add all aggregation recipe --- .../transect_case_aggregation_all.yaml | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/CSET/recipes/level_fields/transect_case_aggregation_all.yaml diff --git a/src/CSET/recipes/level_fields/transect_case_aggregation_all.yaml b/src/CSET/recipes/level_fields/transect_case_aggregation_all.yaml new file mode 100644 index 000000000..c4aef3ffd --- /dev/null +++ b/src/CSET/recipes/level_fields/transect_case_aggregation_all.yaml @@ -0,0 +1,42 @@ +category: Transect +title: $MODEL_NAME Transect of $VARNAME Aggregation over all cases. +description: | + Extracts a $VERTICAL_COORDINATE transect for $VARNAME between two points and + plots it. + + Start coordinate: `$START_COORDS` + End coordinate: `$FINISH_COORDS` + +steps: + - operator: read.read_cubes + file_paths: $INPUT_PATHS + constraint: + operator: constraints.combine_constraints + cell_method_constraint: + operator: constraints.generate_cell_methods_constraint + cell_methods: [] + var_constraint: + operator: constraints.generate_var_constraint + varname: $VARNAME + level_constraint: + operator: constraints.generate_level_constraint + coordinate: $VERTICAL_COORDINATE + levels: '*' + subarea_type: $SUBAREA_TYPE + subarea_extent: $SUBAREA_EXTENT + + - operator: aggregate.ensure_aggregatable_across_cases + + - operator: collapse.collapse + coordinate: time + method: MEAN + + - operator: transect.calc_transect + startcoords: $START_COORDS + endcoords: $FINISH_COORDS + + - operator: plot.spatial_contour_plot + sequence_coordinate: time + + - operator: write.write_cube_to_nc + overwrite: True From c3a864dc748db61ae6ec9019bd4a51b246eee1a2 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 30 Mar 2026 16:43:36 +0100 Subject: [PATCH 16/17] add lead time aggregation recipe --- .../transect_case_aggregation_lead_time.yaml | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/CSET/recipes/level_fields/transect_case_aggregation_lead_time.yaml diff --git a/src/CSET/recipes/level_fields/transect_case_aggregation_lead_time.yaml b/src/CSET/recipes/level_fields/transect_case_aggregation_lead_time.yaml new file mode 100644 index 000000000..68861a5e9 --- /dev/null +++ b/src/CSET/recipes/level_fields/transect_case_aggregation_lead_time.yaml @@ -0,0 +1,42 @@ +category: Transect +title: $MODEL_NAME Transect of $VARNAME Aggregation by lead time. +description: | + Extracts a $VERTICAL_COORDINATE transect for $VARNAME between two points and + plots it. + + Start coordinate: `$START_COORDS` + End coordinate: `$FINISH_COORDS` + +steps: + - operator: read.read_cubes + file_paths: $INPUT_PATHS + constraint: + operator: constraints.combine_constraints + cell_method_constraint: + operator: constraints.generate_cell_methods_constraint + cell_methods: [] + var_constraint: + operator: constraints.generate_var_constraint + varname: $VARNAME + level_constraint: + operator: constraints.generate_level_constraint + coordinate: $VERTICAL_COORDINATE + levels: '*' + subarea_type: $SUBAREA_TYPE + subarea_extent: $SUBAREA_EXTENT + + - operator: aggregate.ensure_aggregatable_across_cases + + - operator: collapse.collapse + coordinate: "forecast_reference_time" + method: MEAN + + - operator: transect.calc_transect + startcoords: $START_COORDS + endcoords: $FINISH_COORDS + + - operator: plot.spatial_contour_plot + sequence_coordinate: forecast_period + + - operator: write.write_cube_to_nc + overwrite: True From 4aec34e938fd2b8d567f7d274adfbc81e72cbe97 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 30 Mar 2026 16:44:00 +0100 Subject: [PATCH 17/17] add valid time aggregation recipe --- ...ansect_case_aggregation_validity_time.yaml | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/CSET/recipes/level_fields/transect_case_aggregation_validity_time.yaml diff --git a/src/CSET/recipes/level_fields/transect_case_aggregation_validity_time.yaml b/src/CSET/recipes/level_fields/transect_case_aggregation_validity_time.yaml new file mode 100644 index 000000000..45b21352d --- /dev/null +++ b/src/CSET/recipes/level_fields/transect_case_aggregation_validity_time.yaml @@ -0,0 +1,41 @@ +category: Transect +title: $MODEL_NAME Transect of $VARNAME Aggregation by validity time. +description: | + Extracts a $VERTICAL_COORDINATE transect for $VARNAME between two points and + plots it. + + Start coordinate: `$START_COORDS` + End coordinate: `$FINISH_COORDS` + +steps: + - operator: read.read_cubes + file_paths: $INPUT_PATHS + constraint: + operator: constraints.combine_constraints + cell_method_constraint: + operator: constraints.generate_cell_methods_constraint + cell_methods: [] + var_constraint: + operator: constraints.generate_var_constraint + varname: $VARNAME + level_constraint: + operator: constraints.generate_level_constraint + coordinate: $VERTICAL_COORDINATE + levels: '*' + subarea_type: $SUBAREA_TYPE + subarea_extent: $SUBAREA_EXTENT + + - operator: aggregate.ensure_aggregatable_across_cases + + - operator: collapse.collapse_by_validity_time + method: MEAN + + - operator: transect.calc_transect + startcoords: $START_COORDS + endcoords: $FINISH_COORDS + + - operator: plot.spatial_contour_plot + sequence_coordinate: time + + - operator: write.write_cube_to_nc + overwrite: True