Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
0b52c83
Recipe for histograms of masked Nimrod accumulations #2113
BernardClaxton Jun 4, 2026
391c433
Loader for Nimrod masked histograms #2113
BernardClaxton Jun 4, 2026
8a091ec
Function to produce Nimrod masked cubes #2113
BernardClaxton Jun 4, 2026
a1fc575
Recipe to produce mean time series of masked Nimrod accumulations #2113
BernardClaxton Jun 4, 2026
4cfbdfd
Loader for recipe to produce mean time series of masked Nimrod accumu…
BernardClaxton Jun 4, 2026
fa0f7cd
Nimrod weights 2-d plots to use gray for out of radar range domain #…
BernardClaxton Jun 4, 2026
d6d441b
jaml recipe for plotting masked model rainfall #2113
BernardClaxton Jun 18, 2026
93825e2
function radar_apply_mask in radar_filter.py #2113
BernardClaxton Jun 18, 2026
99cc537
radar_apply_mask now uses function apply_mask in filters.py #2113
BernardClaxton Jun 18, 2026
3268736
loaders for radar_mask_model.yaml #2113
BernardClaxton Jun 18, 2026
33a20f2
for 2-d surface plots replaced np.min, np.max, np.mean with np.nanmin…
BernardClaxton Jun 18, 2026
4b81b55
Grey out domain beyond radar range for rainfall masked spatial plots …
BernardClaxton Jun 19, 2026
493ddfa
When regridding model using nimrod wts field, set extrapolated values…
BernardClaxton Jul 20, 2026
79d9476
Radar operators in radar_filter.py #2113
BernardClaxton Aug 27, 2026
da7576d
Radar loaders #2113
BernardClaxton Aug 27, 2026
2d973f2
Radar recipe for domain mean histogram #2113
BernardClaxton Aug 27, 2026
a9e0d13
Radar recipe for histogram sequences #2113
BernardClaxton Aug 27, 2026
6073ff9
Radar loaders #2113
BernardClaxton Aug 27, 2026
7acc815
recipe histogram mean #2113
BernardClaxton Aug 27, 2026
36dd595
recipe 2-d radar plots #2113
BernardClaxton Aug 27, 2026
ae5ba6c
recipe radar_dev3 plots #2113
BernardClaxton Aug 27, 2026
596cace
recipe radar_masked_mean_time_series #2113
BernardClaxton Aug 27, 2026
b4390e1
recipe radar_masked_time_series #2113
BernardClaxton Aug 27, 2026
0b096ae
radar rose-meta.conf #2113
BernardClaxton Aug 27, 2026
5323901
modified collapse operator #2113
BernardClaxton Sep 3, 2026
282e416
modified radar_filter operator #2113
BernardClaxton Sep 3, 2026
d2aff8b
Added NIMROD_RADAR_OBS guards in radar loaders #2113
BernardClaxton Sep 9, 2026
2c7731d
fixed a radar recipe to produce mean histograms over a case study #2113
BernardClaxton Sep 9, 2026
5c0407f
wire SEQUENCE into radar histogram recipe.
Sylviabohnenstengel Sep 9, 2026
39dcac2
remove time mean of spatial fields for calculation of radar histograms.
Sylviabohnenstengel Sep 9, 2026
2d3e72d
update collapse operator #2113
BernardClaxton Sep 10, 2026
0901dd3
update radar_filter operator #2113
BernardClaxton Sep 10, 2026
9870de7
Added +1 hour offset to the start time of the radar files to fetch #…
BernardClaxton Sep 10, 2026
20f101d
New button in Rose GUI to select radar histogram outputs #2113
BernardClaxton Sep 10, 2026
99478b6
New recipe and loaders for Nimrod radar histograms #2113
BernardClaxton Sep 10, 2026
6544638
Tidied radar histogram recipe #2113
BernardClaxton Sep 10, 2026
bac16f5
New radar Rose GUI button for PROCESS_RADAR_2D #2113
BernardClaxton Sep 10, 2026
5a6e228
Tidied recipe to produce 2D surface plots of radar rainfall and weigh…
BernardClaxton Sep 10, 2026
5e0aa20
Loader to produce 2D surface plots of radar rainfall and weights #2113
BernardClaxton Sep 10, 2026
fcafe65
Merge branch 'main' into 2113_nimrod_regrid
BernardClaxton Sep 10, 2026
b327135
New recipe for common domain 2D radar plots #2113
BernardClaxton Sep 10, 2026
2327952
Recipe for 2D plots of model rainfall over domain common to both mode…
BernardClaxton Sep 11, 2026
ae2380d
Recipe for 2D plots of radar rainfall over domain common to both mode…
BernardClaxton Sep 11, 2026
24701dc
fetch_nimrod.py now retrieves the correct number of radar files #2113
BernardClaxton Sep 14, 2026
e388bcc
Put strict_title into ploy.py to turn off prepending plot tiltes with…
BernardClaxton Sep 14, 2026
141c246
Put strict_title into a recipe to turn off prepending plot title with…
BernardClaxton Sep 14, 2026
145aab7
Put strict_title into a recipe to turn off prepending plot title with…
BernardClaxton Sep 14, 2026
175101b
Put strict_title into doc strings in ploy.py #2113
BernardClaxton Sep 15, 2026
b5b7b80
Removed redundant code from radar_filter.radar_apply_mask #2113
BernardClaxton Sep 17, 2026
f048348
Radar loaders updated #2113
BernardClaxton Sep 17, 2026
26bb073
Spare recipe #2113
BernardClaxton Sep 17, 2026
d73552e
Updated recipe description for generic_surface_spatial_plot_sequence_…
BernardClaxton Sep 17, 2026
0bcacf8
Updated recipe description for model_radar_common_domain_2d_model.yam…
BernardClaxton Sep 17, 2026
d0c00fe
Updated recipe description for model_radar_common_domain_2d_radar.yam…
BernardClaxton Sep 17, 2026
c5683f3
Updated recipe description for radar_common_domain_histogram.yaml #2113
BernardClaxton Sep 17, 2026
0e33a0c
Updated radar loaders #2113
BernardClaxton Sep 17, 2026
a2433b4
Typos in rose-meta.conf for Rose GUI #2113
BernardClaxton Sep 17, 2026
771adf7
Tidied operator radar_filter.mask_list #2113
BernardClaxton Sep 17, 2026
aa56edf
Tidied operator radar_filter.mask_by_weights #2113
BernardClaxton Sep 17, 2026
d45db95
Tidied operator radar_filter.radar_apply_mask #2113
BernardClaxton Sep 17, 2026
6c0a7ad
Tidied operator radar_filter.radar_mask #2113
BernardClaxton Sep 17, 2026
48192ab
Tidied and added to docstring operator radar_filter.radar_mask #2113
BernardClaxton Sep 17, 2026
5bfa342
Tidied and added to docstring operator radar_filter.radar_mask_loop #…
BernardClaxton Sep 17, 2026
d8b2c68
Tidied and added to docstring operator radar_filter.match_varname_and…
BernardClaxton Sep 17, 2026
f40b3a6
Model boundary margin is now templated in model_radar_common_domain_2…
BernardClaxton Sep 17, 2026
0775861
Model boundary margin is now templated in model_radar_common_domain_2…
BernardClaxton Sep 17, 2026
ad49d00
Model boundary margin is now templated in radar_common_domain_histogr…
BernardClaxton Sep 17, 2026
ae32363
Model boundary margin is now templated in radar loaders #2113
BernardClaxton Sep 17, 2026
6d06605
Radar loader for model common domain loops over models #2113
BernardClaxton Sep 17, 2026
5ebe827
Tidied radar loaders #2113
BernardClaxton Sep 21, 2026
0f66a89
Test for radar_filter.mask_list #2113
BernardClaxton Sep 21, 2026
caf8c62
Changed L537 in _colormaps.py from logging.info to logger.info #2113
BernardClaxton Sep 21, 2026
103ada3
Updated rose-suite.conf.example with PROCESS_RADAR_2D and PROCESSS_RA…
BernardClaxton Sep 21, 2026
8af3a65
Cleaned collapse.py - removed test prints #2113
BernardClaxton Sep 21, 2026
4e42c4d
Removed commented out lines from meta/observations/rose-meta.conf #2113
BernardClaxton Sep 21, 2026
7af6139
Added the radar processing to trigger in meta/observations/rose-meta.…
BernardClaxton Sep 21, 2026
a61ebfc
Updated test_fetch_nimrod to account for the +1hr for radar accumulat…
BernardClaxton Sep 21, 2026
4875f8e
Added test for radar_filter.mask_by_weights #2113
BernardClaxton Sep 21, 2026
d0c2029
Added test for radar_filter.mask_by_weights #2113
BernardClaxton Sep 21, 2026
f691244
Minor edit to a radar test #2113
BernardClaxton Sep 22, 2026
83ed54d
Minor typo corrected #2113
BernardClaxton Sep 22, 2026
d52c766
Test for radar_filter.match_varname_and_units #2113
BernardClaxton Sep 23, 2026
6315d47
Tidied radar_filter.py #2113
BernardClaxton Sep 24, 2026
cdee488
Added some radar tests #2113
BernardClaxton Sep 24, 2026
ea30b52
Added more tests to test_radar_filter.py #2113
BernardClaxton Oct 1, 2026
33a3ba0
Added yet more tests to test_radar_filter.py #2113
BernardClaxton Oct 1, 2026
86d2784
Apply suggestion from @Sylviabohnenstengel
james-a-f-ross Oct 16, 2026
51fb232
Update src/CSET/operators/radar_filter.py
james-a-f-ross Oct 16, 2026
9b4dcaa
Update src/CSET/loaders/radar.py
james-a-f-ross Oct 16, 2026
b753505
Update src/CSET/operators/radar_filter.py
james-a-f-ross Oct 16, 2026
d7b621c
type hinting
james-a-f-ross Oct 16, 2026
3bfbe9c
outputs -> required_outputs. match statement instead of if statements
james-a-f-ross Oct 16, 2026
1a0bcb4
simiplify radar_mask_loop
james-a-f-ross Oct 16, 2026
d381a8e
fix unit tests
james-a-f-ross Oct 16, 2026
465fc5b
Update src/CSET/loaders/radar.py
james-a-f-ross Oct 16, 2026
d491106
combine conditional
james-a-f-ross Oct 16, 2026
660b776
fix uneven boudnary
james-a-f-ross Oct 16, 2026
e597460
make mask boundary seperate function and unit test it
james-a-f-ross Oct 16, 2026
65ed6d7
move radar recipes to own folder
james-a-f-ross Oct 16, 2026
b2642c3
removing some redundant code
james-a-f-ross Oct 16, 2026
df5aace
Update src/CSET/recipes/surface_fields/nimrod/radar_common_domain_2d_…
james-a-f-ross Oct 16, 2026
fc74417
Update src/CSET/recipes/surface_fields/nimrod/radar_common_domain_his…
james-a-f-ross Oct 16, 2026
245f356
Update src/CSET/recipes/surface_fields/nimrod/radar_common_domain_his…
james-a-f-ross Oct 16, 2026
59c9546
code cleanup
james-a-f-ross Oct 20, 2026
66e37df
fix test
james-a-f-ross Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 50 additions & 43 deletions src/CSET/cset_workflow/app/fetch_nimrod/bin/fetch_nimrod.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import json
import logging
import os
from datetime import datetime
from datetime import datetime, timedelta
from pathlib import Path

import iris
Expand Down Expand Up @@ -94,82 +94,89 @@ def retrieve_nimrod():
* ANALYSIS_LENGTH
* ROSE_DATAC
"""

# Grab the environment variables required for handling Nimrod data.
def _load_nimrod(path: Path) -> iris.cube.Cube | None:
"""Load a Nimrod file, or return None if it's missing."""
if not path.exists():
logger.warning("Nimrod file not found: %s", path)
return None
return iris.load_cube(path)

v = _get_needed_environment_variables_nimrod()

# Grab the Nimrod handling dictionary.
with open(nimrod_met_office, "rt") as fp: # pragma: no cover
nimrod_dict = json.load(fp)

# Form the Nimrod start and end dates.
date_start = v["data_time"]
date_end = v["data_time"] + v["forecast_length"]

# Loop over the required Nimrod fields, i.e. 1km 2km or xkm rainfall
# accumulation composites or the 5 minute rainfall rate composites.
for nimrod_field in v["field"]:
if nimrod_field:
logger.info("Processing Nimrod field: %s", nimrod_field)

nimrod_cfg = nimrod_dict[nimrod_field]
# Prepare the output directory for the Nimrod field.
nimrod_dir = f"{v['rose_datac']}/data/{nimrod_dict[nimrod_field]['obs_id']}"
nimrod_dir = Path(v["rose_datac"]) / "data" / nimrod_cfg["obs_id"]
os.makedirs(nimrod_dir, exist_ok=True)
logger.info("Cylc-run Nimrod directory: %s", nimrod_dir)

# Prepare the output directory for the Nimrod weights field.
if nimrod_dict[nimrod_field]["weights_fname"]:
nimrod_dir_wei = (
f"{v['rose_datac']}/data/{nimrod_dict[nimrod_field]['wei_id']}"
)
nimrod_dir_wei = Path(v["rose_datac"]) / "data" / nimrod_cfg["wei_id"]
os.makedirs(nimrod_dir_wei, exist_ok=True)
logger.info("Cylc-run Nimrod weights directory: %s", nimrod_dir_wei)

# Put +1 hour offset for accumulation radar files as

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need the shift? Asking as we are loading radar data with a timestamp ending the hourly accumulation period. so we read an hourly accumulation for 11am which is representative for the accumulation from 10-11. Lfric gives us an instantaneous rainfall rate at 11. Apart from teh required conversions and getting accums from lfric isn't the timestamp actually correct?

@Sylviabohnenstengel Sylvia Bohnenstengel (Sylviabohnenstengel) Oct 15, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been comparing the lfric files containing surface_microphysical_rainfall_rate i.e. the timeproc files and the diagnostic files against UK Nimrod

LFRic: When fetching CYCLE_TASK_CYCLE_POINT=00:00:00

  • timeproc files give seconds since 00:00:00
    point [00:30:00, 01:30:00, 02:30:00]
    bounds [00:00:00, 01:00:00] [01:00:00:00, 02:00:00][02:00:00,03:00:00 ]
  • diagnostics files give seconds since 00:00:00
    point [01:00:00, 02:00:00, 03:00:00]
    bounds [01:00:00, 01:00:00] [02:00:00, 02:00:00][03:00:00, 03:00:00]

hence timeproc gives a meaned (?) field on the halfway point and diagnostics gives a truly instantaneous point.

UKNimrod radar when fetching CYCLE_TASK_CYCLE_POINT=00:00:00
points [00:00:00]
bounds [23:00:00,00:00:00]

For fetching a filename for lfric and radar for CYCLE_TASk_CYCLE_POINT

  • LFRic returns a file with CYCLE_TASk_CYCLE_POINT in the name and data for bounds [CYCLE_TASK_CYCLE_POINT, CYCLE_TASK_CYCLE_POINT+1 ]
  • Radar returns a file name with CYCLE_TASK_CYCLE_POINT in the name and data for bounds [CYCLE_TASK_CYCLE_POINT -1, CYCLE_TASK_CYCLE_POINT]

Hence, we need to shift UK nimrod radar data filename by +1 to fetch the correpsonding time bounds between lfric and radar. I suspect that when outputting accumulations in lfric for a filename with CYCLE_TASK_CYCLE_POINT we need to check if the output bounds still start at [CYCLE_TASK_CYCLE_POINT, CYCLE_TASK_CYCLE_POINT+1] with a point at CYCLE_TASK_CYCLE_POINT+1 or if a point exists at CYCLE_TASK_CYCLE_POINT.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for now we require the +1 correction to CYCLE_TASK_CYCLE_POINT to fetch the correct radar file.

# the time stamps for these files mark the end of the
# accumulation period rather than the beginning.
radar_offset = timedelta(hours=0.0)
if nimrod_field == "Nimrod_comp_xkm":
radar_offset = timedelta(hours=1.0)
if nimrod_field == "Nimrod_comp_1km":
radar_offset = timedelta(hours=1.0)
if nimrod_field == "Nimrod_comp_2km":
radar_offset = timedelta(hours=1.0)
date_start_offset = v["data_time"] + radar_offset

# Process Nimrod data between the start and end dates.
date_use = date_start
while date_use <= date_end:
date_use = date_start_offset
while date_use < date_start_offset + v["forecast_length"]:
# Load the Nimrod data into an Iris cube.
nimrod_obs_exist = "False"
nimrod_obs = (
f"{nimrod_dict[nimrod_field]['basedir']}/"
f"{nimrod_dict[nimrod_field]['obs_dir']}/"
f"{date_use.year}/{date_use.strftime('%Y%m%d%H%M')}"
f"{nimrod_dict[nimrod_field]['obs_fname']}"

nimrod_obs_path = (
Path(nimrod_cfg["basedir"])
/ nimrod_cfg["obs_dir"]
/ str(date_use.year)
/ f"{date_use:%Y%m%d%H%M}{nimrod_cfg['obs_fname']}"
)
try:
nimrod_cube_obs = iris.load_cube(nimrod_obs)
nimrod_obs_exist = "True"
except OSError:
logger.warning("Iris cannot find Nimrod file %s", nimrod_obs)

nimrod_cube_obs = _load_nimrod(nimrod_obs_path)

# Load the Nimrod weights into an Iris cube.
nimrod_weights_exist = "False"
if nimrod_dict[nimrod_field]["weights_fname"] != "":
nimrod_weights = (
f"{nimrod_dict[nimrod_field]['basedir']}/"
f"{nimrod_dict[nimrod_field]['weights_dir']}/"
f"{date_use.year}/{date_use.strftime('%Y%m%d%H%M')}"
f"{nimrod_dict[nimrod_field]['weights_fname']}"
nimrod_cube_weights = None
if nimrod_cfg["weights_fname"]:
nimrod_weights_path = (
Path(nimrod_cfg["basedir"])
/ nimrod_cfg["weights_dir"]
/ str(date_use.year)
/ f"{date_use:%Y%m%d%H%M}{nimrod_cfg['weights_fname']}"
)
try:
nimrod_cube_weights = iris.load_cube(nimrod_weights)
nimrod_weights_exist = "True"
except OSError:
logger.warning(
"Iris cannot find Nimrod weights file %s", nimrod_weights
)

# Ensure that the weights are unitless.
nimrod_cube_weights.units = "1"
nimrod_cube_weights = _load_nimrod(nimrod_weights_path)

if nimrod_cube_weights is not None:
# Ensure that the weights are unitless.
nimrod_cube_weights.units = "1"

# QC the the Nimrod observations using the weights field.
if nimrod_obs_exist == "True" and nimrod_weights_exist == "True":
if nimrod_cube_obs is not None and nimrod_cube_weights is not None:
nimrod_cube_obs, nimrod_cube_weights = apply_radar_weights(
nimrod_cube_obs, nimrod_cube_weights
)

# Now that the accumulation weights have been applied if required,
# write the Nimrod obs to a NetCDF file.
if nimrod_obs_exist == "True":
if nimrod_cube_obs is not None:
filename_obs_nc = (
f"{nimrod_dir}/{date_use.strftime('%Y%m%d%H%M')}"
f"_{nimrod_dict[nimrod_field]['obs_id']}"
Expand All @@ -179,7 +186,7 @@ def retrieve_nimrod():

# Write the Nimrod weights to a NetCDF file if the switch
# for this is set in v["weights"].
if v["weights"] == "True" and nimrod_weights_exist == "True":
if v["weights"] == "True" and nimrod_cube_weights is not None:
filename_wei_nc = (
f"{nimrod_dir_wei}/{date_use.strftime('%Y%m%d%H%M')}"
f"_{nimrod_dict[nimrod_field]['obs_id']}_weights"
Expand All @@ -194,6 +201,6 @@ def retrieve_nimrod():
)


# Run the function that fetches the NImrod radar obs.
# Run the function that fetches the Nimrod radar obs.
if __name__ == "__main__":
retrieve_nimrod()
28 changes: 26 additions & 2 deletions src/CSET/cset_workflow/meta/observations/rose-meta.conf
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ trigger=template variables=NIMROD_COMP_XKM: True;
template variables=NIMROD_COMP_2KM: True;
template variables=NIMROD_WEIGHTS: True;
template variables=NIMROD_COMP_5MIN: True;
template variables=PROCESS_RADAR_2D: True;
template variables=PROCESS_RADAR_HISTOGRAMS: True;

[template variables=CARDINGTON_OBS]
ns=Observations
Expand Down Expand Up @@ -223,8 +225,8 @@ title=Radar observations

[template variables=NIMROD_COMP_XKM]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does XKM actually mean? Description suggests 5km??

ns=Observations/Radar
title=UK Nimrod radar accumulated rainfall
description=Default UK Nimrod rainfall accumulation
title=UK Nimrod radar accumulated rainfall 5km gridding
description=5km gridded UK Nimrod rainfall accumulation
help=Switch to select default gridded UK Nimrod rainfall accumulation.
compulsory=true
type=python_boolean
Expand Down Expand Up @@ -269,6 +271,28 @@ compulsory=true
type=python_boolean
sort-key=nimrod-radar-7

[template variables=PROCESS_RADAR_2D]
ns=Observations/Radar
title=Produce Nimrod 2D surface plots
description=Produce 2D surface plots of radar observations.
help=Switch to produce 2D surface plots utilising Nimrod radar observations.
Plots are produced of the 2D surface rainfall for each of the selected Nimrod sources.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more precision needed: Nimrod provides rainfall amounts

Plots of the associated Nimrod weights fields are also produced.
compulsory=true
type=python_boolean
sort-key=nimrod-radar-8

[template variables=PROCESS_RADAR_HISTOGRAMS]
ns=Observations/Radar
title=Produce Nimrod radar histograms
description=Produce histograms incorporating radar observations.
help=Switch to produce histograms utilising Nimrod radar observations.
The histograms will combine both model rainfall and radar observations.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will likely change when lfric gives us accumulations as well, but worth being precise her that Nimrod gives as amounts as an accumulation for sum products and a 5min "snapshot" for the other product, whereas the models currently gives us rainfall_rate i.e. amount per time as a snapshot

The model rainfall variable is selected in the Rose tab Diagnostics/Surface (2D) fields.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? Other code suggests rainfall variable is fixed, i.e.
"MODEL_VARNAME": "surface_microphysical_rainfall_rate",

compulsory=true
type=python_boolean
sort-key=nimrod-radar-9a

################################################################
# Cardington UK Research Observations
[ns=Observations/Cardington]
Expand Down
2 changes: 2 additions & 0 deletions src/CSET/cset_workflow/rose-suite.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ MODERATE_RAIN_PRESENCE_SPATIAL_PLOT=False
!!NIMROD_COMP_2KM=False
!!NIMROD_COMP_5MIN=False
!!NIMROD_COMP_XKM=False
!!PROCESS_RADAR_2D=False
!!PROCESS_RADAR_HISTOGRAMS=False
NIMROD_RADAR_OBS=False
!!NIMROD_WEIGHTS=False
!!PLEVEL_TRANSECT_AGGREGATION=False,False,False,False
Expand Down
Loading
Loading