From 07519bd20ab29938cd5961f1a4b54738a2f5fff5 Mon Sep 17 00:00:00 2001 From: ukmo-huw-lewis Date: Sun, 28 Jun 2026 20:30:23 +0100 Subject: [PATCH 1/9] Introduce cset_gallery functionality --- docs/source/conf.py | 28 ++++++++ docs/source/index.rst | 3 + docs/source/reference/cli.rst | 4 +- .../cset_gallery/examples/README.rst | 1 + .../cset_gallery/examples/custom/README.rst | 2 + .../custom/plot_surface_spatial_cutout.py | 69 +++++++++++++++++++ .../plot_surface_spatial_global_cutout.py | 69 +++++++++++++++++++ .../cset_gallery/examples/line/README.rst | 4 ++ .../examples/line/plot_line_timeseries.py | 63 +++++++++++++++++ .../cset_gallery/examples/spatial/README.rst | 4 ++ .../examples/spatial/plot_surface_spatial.py | 58 ++++++++++++++++ .../spatial/plot_surface_spatial_global.py | 58 ++++++++++++++++ docs/source/reference/cset_gallery/index.rst | 14 ++++ docs/source/reference/index.rst | 1 + pyproject.toml | 1 + src/CSET/operators/plot.py | 24 ++++--- 16 files changed, 393 insertions(+), 10 deletions(-) create mode 100644 docs/source/reference/cset_gallery/examples/README.rst create mode 100644 docs/source/reference/cset_gallery/examples/custom/README.rst create mode 100644 docs/source/reference/cset_gallery/examples/custom/plot_surface_spatial_cutout.py create mode 100644 docs/source/reference/cset_gallery/examples/custom/plot_surface_spatial_global_cutout.py create mode 100644 docs/source/reference/cset_gallery/examples/line/README.rst create mode 100644 docs/source/reference/cset_gallery/examples/line/plot_line_timeseries.py create mode 100644 docs/source/reference/cset_gallery/examples/spatial/README.rst create mode 100644 docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial.py create mode 100644 docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_global.py create mode 100644 docs/source/reference/cset_gallery/index.rst diff --git a/docs/source/conf.py b/docs/source/conf.py index 2af96ce82..f2b184903 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -4,6 +4,8 @@ https://www.sphinx-doc.org/en/master/usage/configuration.html """ +from sphinx_gallery.sorting import ExplicitOrder + # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information @@ -20,6 +22,7 @@ "sphinx.ext.napoleon", "sphinx.ext.doctest", "sphinx.ext.extlinks", + "sphinx_gallery.gen_gallery", ] # -- Options for HTML output ------------------------------------------------- @@ -61,3 +64,28 @@ # Met Office internal webserver. r"https://wwwspice/.+", ] + +# -- Sphinx gallery config ---------------------------------------------------- + +sphinx_gallery_conf = { + "plot_gallery": True, + "examples_dirs": "reference/cset_gallery/examples", # input scripts + "gallery_dirs": "reference/cset_gallery/generated", # output pages + "filename_pattern": r"\.py$", + "image_scrapers": ("matplotlib",), + "thumbnail_size": (800, 600), + "capture_repr": (), # disable capture of printed / returned output + "nested_sections": False, + "subsection_order": ExplicitOrder( + [ + "reference/cset_gallery/examples/spatial", + "reference/cset_gallery/examples/line", + "reference/cset_gallery/examples/custom", + ] + ), +} + +suppress_warnings = [ + "toc.not_included", + "toc.excluded", +] diff --git a/docs/source/index.rst b/docs/source/index.rst index 53c35565c..1b270586a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -26,6 +26,9 @@ Useful links `Source Code`_ | `Issue Tracker`_ | Releases_ | `Discussion Forum`_ + +For examples of CSET outputs, see :doc:`reference/cset_gallery/index`. + For information on how to use CSET, see :doc:`getting-started/index`. For information on getting involved as a developer, see diff --git a/docs/source/reference/cli.rst b/docs/source/reference/cli.rst index 5341f9b30..16bb7fb64 100644 --- a/docs/source/reference/cli.rst +++ b/docs/source/reference/cli.rst @@ -1,5 +1,5 @@ -CLI Usage -========= +Command Line Usage (CLI) +======================== .. _cset-bake-command: diff --git a/docs/source/reference/cset_gallery/examples/README.rst b/docs/source/reference/cset_gallery/examples/README.rst new file mode 100644 index 000000000..a8e5234a2 --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/README.rst @@ -0,0 +1 @@ +.. orphan diff --git a/docs/source/reference/cset_gallery/examples/custom/README.rst b/docs/source/reference/cset_gallery/examples/custom/README.rst new file mode 100644 index 000000000..2d77c89de --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/custom/README.rst @@ -0,0 +1,2 @@ +Customising CSET outputs +======================== diff --git a/docs/source/reference/cset_gallery/examples/custom/plot_surface_spatial_cutout.py b/docs/source/reference/cset_gallery/examples/custom/plot_surface_spatial_cutout.py new file mode 100644 index 000000000..0e6f3bc6e --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/custom/plot_surface_spatial_cutout.py @@ -0,0 +1,69 @@ +""" +Trim edge gridcells +=================== + +Generate spatial map of a 2D field over selected sub-region of data with domain edges trimmed. + +Spatial maps are generated using either CSET operators :py:mod:`CSET.operators.plot.spatial_pcolormesh_plot` or :py:mod:`CSET.operators.plot.spatial_contour_plot`. + +General functionality is provided using :doc:`CSET recipe ` ``generic_surface_spatial_plot_sequence.yaml`` + + +A) Using *cset bake* on the command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- See :doc:`/reference/cset_gallery/generated/spatial/plot_surface_spatial` for general settings. +- Set ``SUBAREA_TYPE`` and ``SUBAREA_EXTENT`` to select trim widths, and use ``SUBAREA_NAME`` to control labelling. +- Example to generate spatial maps for selected sub-area of ``VARNAME`` for all output times: + +.. code-block:: + + cset cookbook generic_surface_spatial_plot_sequence + cset bake -i "input_data_path" -o "my_output_path" + -r generic_surface_spatial_plot_sequence + --VARNAME="temperature_at_screen_level" + --MODEL_NAME="my_model_label" + --METHOD="" + --SUBAREA_TYPE='gridcells' --SUBAREA_EXTENT=[3, 2, 3, 1] --SUBAREA_NAME='' + [-s STYLE_FILE] [--plot-resolution PLOT_RESOLUTION] [--skip-write] + + +B) Configuring the *cset_workflow* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Update workflow configuration settings via ``rose edit`` GUI or in ``rose-suite.conf`` file. +- Complete ``General setup options`` and ``Cycling and Model options`` details - see :doc:`/usage/workflow-configure`. +- Set ``SELECT_SUBAREA`` to ``True``, set ``SUBAREA_TYPE`` to ``gridcells`` and set ``SUBAREA_EXTENT``. +- Set other required configuration options on ``Diagnostics / Surface (2D) fields`` panel. + +:: + + SELECT_SUBAREA = True + SPATIAL_SURFACE_FIELD = True + SUBAREA_TYPE = 'gridcells' + SUBAREA_EXTENT = [3, 2, 3, 1] + SUBAREA_NAME = '' + SURFACE_FIELDS = ['temperature_at_screen_level', , ...] + + +C) Example python code +^^^^^^^^^^^^^^^^^^^^^^ +""" + +import CSET.operators.plot as cset_plot +import CSET.operators.read as cset_read + +# Set path to input data +file_path = "../../../../../../tests/test_data/air_temp.nc" + +# Read selected variable(s) of interest. +# Use read_cube parameters to control edge trim selection. +cube = cset_read.read_cubes( + file_path, + ["temperature_at_screen_level"], + subarea_type="gridcells", + subarea_extent=[3, 2, 3, 1], +)[0] + +# Plot single example frame using spatial_contour_plot +cset_plot.spatial_contour_plot(cube[-1]) diff --git a/docs/source/reference/cset_gallery/examples/custom/plot_surface_spatial_global_cutout.py b/docs/source/reference/cset_gallery/examples/custom/plot_surface_spatial_global_cutout.py new file mode 100644 index 000000000..6d6bd15db --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/custom/plot_surface_spatial_global_cutout.py @@ -0,0 +1,69 @@ +""" +Select lat-lon subarea +====================== + +Generate spatial map of a 2D field over selected sub-region of data. + +Spatial maps are generated using either CSET operators :py:mod:`CSET.operators.plot.spatial_pcolormesh_plot` or :py:mod:`CSET.operators.plot.spatial_contour_plot`. + +General functionality is provided using :doc:`CSET recipe ` ``generic_surface_spatial_plot_sequence.yaml`` + + +A) Using *cset bake* on the command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- See :doc:`/reference/cset_gallery/generated/spatial/plot_surface_spatial_global` for general settings. +- Set ``SUBAREA_TYPE`` and ``SUBAREA_EXTENT`` to select sub-region, and use ``SUBAREA_NAME`` to control labelling. +- Example to generate spatial maps for selected sub-area of ``VARNAME`` for all output times: + +.. code-block:: + + cset cookbook generic_surface_spatial_plot_sequence + cset bake -i "input_data_path" -o "my_output_path" + -r generic_surface_spatial_plot_sequence + --VARNAME="temperature_at_screen_level" + --MODEL_NAME="my_model_label" + --METHOD="" + --SUBAREA_TYPE='realworld' --SUBAREA_EXTENT=[-40.0, 40.0, -20.0, 55.0] --SUBAREA_NAME='Africa' + [-s STYLE_FILE] [--plot-resolution PLOT_RESOLUTION] [--skip-write] + + +B) Configuring the *cset_workflow* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Update workflow configuration settings via ``rose edit`` GUI or in ``rose-suite.conf`` file. +- Complete ``General setup options`` and ``Cycling and Model options`` details - see :doc:`/usage/workflow-configure`. +- Set ``SELECT_SUBAREA`` to ``True``, choose ``SUBAREA_TYPE`` and set ``SUBAREA_EXTENT`` and ``SUBAREA_NAME``. +- Set other required configuration options on ``Diagnostics / Surface (2D) fields`` panel. + +:: + + SELECT_SUBAREA = True + SPATIAL_SURFACE_FIELD = True + SUBAREA_TYPE = 'realworld' + SUBAREA_EXTENT = [-40.0, 40.0, -20.0, 55.0] + SUBAREA_NAME = 'Africa' + SURFACE_FIELDS = ['temperature_at_screen_level', , ...] + + +C) Example python code +^^^^^^^^^^^^^^^^^^^^^^ +""" + +import CSET.operators.plot as cset_plot +import CSET.operators.read as cset_read + +# Set path to input data +file_path = "../../../../../../tests/test_data/air_temperature_global.nc" + +# Read selected variable(s) of interest. +# Use read_cube parameters to control subarea selection. +cube = cset_read.read_cube( + file_path, + ["temperature_at_screen_level"], + subarea_type="realworld", + subarea_extent=[-40.0, 40.0, -20.0, 55.0], +) + +# Plot single example frame using spatial_pcolormesh_plot +cset_plot.spatial_pcolormesh_plot(cube) diff --git a/docs/source/reference/cset_gallery/examples/line/README.rst b/docs/source/reference/cset_gallery/examples/line/README.rst new file mode 100644 index 000000000..4df0d86f6 --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/line/README.rst @@ -0,0 +1,4 @@ +.. orphan + +Line plots +========== diff --git a/docs/source/reference/cset_gallery/examples/line/plot_line_timeseries.py b/docs/source/reference/cset_gallery/examples/line/plot_line_timeseries.py new file mode 100644 index 000000000..faa243e5b --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/line/plot_line_timeseries.py @@ -0,0 +1,63 @@ +""" +Time series plot +================ + +Generate time series of region-averaged field. + +Line are generated using either CSET operators :py:mod:`CSET.operators.plot.plot_line_series`. + +General functionality is provided using :doc:`CSET recipe ` ``generic_surface_domain_mean_time_series.yaml`` + + +A) Using *cset bake* on the command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Access recipe file using ``cset cookbook``. +- Set required recipe inputs on command-line (or as environment variables for greater flexibility). +- Example to generate full-domain spatial maps of ``VARNAME`` for all output times: + +.. code-block:: + + cset cookbook generic_surface_domain_mean_time_series + cset bake -i "input_data_path" ["input_data_path2" "input_data_path3" "..."] -o "my_output_path" + -r generic_surface_domain_mean_time_series + --VARNAME="temperature_at_screen_level" + --MODEL_NAME="my_model_label" "my_model_label2" "my_model_label3" "..." + --SUBAREA_TYPE='None' --SUBAREA_EXTENT='None' --SUBAREA_NAME='None' + [-s STYLE_FILE] [--plot-resolution PLOT_RESOLUTION] [--skip-write] + + +B) Configuring the *cset_workflow* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Update workflow configuration settings via ``rose edit`` GUI or in ``rose-suite.conf`` file. +- Complete ``General setup options`` and ``Cycling and Model options`` details - see :doc:`/usage/workflow-configure`. +- Set required configuration options on ``Diagnostics / Surface (2D) fields`` panel: + +:: + + SURFACE_FIELDS = ['temperature_at_screen_level', , ...] + TIMESERIES_SURFACE_FIELD = True + + +C) Example python code +^^^^^^^^^^^^^^^^^^^^^^ +""" + +import CSET.operators.collapse as cset_collapse +import CSET.operators.plot as cset_plot +import CSET.operators.read as cset_read + +# Set path to input data +file_paths = "../../../../../../tests/test_data/long_forecast_air_temp_fcst_1.nc" + +# Read selected variable(s) of interest +cubes = cset_read.read_cubes(file_paths, ["temperature_at_screen_level"]) + +# Collapse input data over selected dimensions +collapsed_cubes = cset_collapse.collapse( + cubes, ["grid_latitude", "grid_longitude"], "MEAN" +) + +# Plot domain mean time series +cset_plot.plot_line_series(collapsed_cubes) diff --git a/docs/source/reference/cset_gallery/examples/spatial/README.rst b/docs/source/reference/cset_gallery/examples/spatial/README.rst new file mode 100644 index 000000000..702349758 --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/spatial/README.rst @@ -0,0 +1,4 @@ +.. orphan + +Spatial plots +============= diff --git a/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial.py b/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial.py new file mode 100644 index 000000000..4967b9465 --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial.py @@ -0,0 +1,58 @@ +""" +Regional spatial plot +===================== + +Generate spatial map of a 2D field (regional data example). + +Spatial maps are generated using either CSET operators :py:mod:`CSET.operators.plot.spatial_pcolormesh_plot` or :py:mod:`CSET.operators.plot.spatial_contour_plot`. + +General functionality is provided using :doc:`CSET recipe ` ``generic_surface_spatial_plot_sequence.yaml`` + + +A) Using *cset bake* on the command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Access recipe file using ``cset cookbook``. +- Set required recipe inputs on command-line (or as environment variables for greater flexibility). +- Example to generate full-domain spatial maps of ``VARNAME`` for all output times: + +.. code-block:: + + cset cookbook generic_surface_spatial_plot_sequence + cset bake -i "input_data_path" -o "my_output_path" + -r generic_surface_spatial_plot_sequence + --VARNAME="temperature_at_screen_level" + --MODEL_NAME="my_model_label" + --METHOD="" + --SUBAREA_TYPE='None' --SUBAREA_EXTENT='None' --SUBAREA_NAME='None' + [-s STYLE_FILE] [--plot-resolution PLOT_RESOLUTION] [--skip-write] + + +B) Configuring the *cset_workflow* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Update workflow configuration settings via ``rose edit`` GUI or in ``rose-suite.conf`` file. +- Complete ``General setup options`` and ``Cycling and Model options`` details - see :doc:`/usage/workflow-configure`. +- Set required configuration options on ``Diagnostics / Surface (2D) fields`` panel: + +:: + + SURFACE_FIELDS = ['temperature_at_screen_level', , ...] + SPATIAL_SURFACE_FIELD = True + + +C) Example python code +^^^^^^^^^^^^^^^^^^^^^^ +""" + +import CSET.operators.plot as cset_plot +import CSET.operators.read as cset_read + +# Set path to input data +file_path = "../../../../../../tests/test_data/air_temp.nc" + +# Read selected variable(s) of interest +cube = cset_read.read_cubes(file_path, ["temperature_at_screen_level"])[0] + +# Plot single example frame (final time output only, using spatial_contour_plot) +cset_plot.spatial_contour_plot(cube[-1]) diff --git a/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_global.py b/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_global.py new file mode 100644 index 000000000..ffedff952 --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_global.py @@ -0,0 +1,58 @@ +""" +Global spatial plot +=================== + +Generate spatial map of a 2D field (global data example). + +Spatial maps are generated using either CSET operators :py:mod:`CSET.operators.plot.spatial_pcolormesh_plot` or :py:mod:`CSET.operators.plot.spatial_contour_plot`. + +General functionality is provided using :doc:`CSET recipe ` ``generic_surface_spatial_plot_sequence.yaml``. + + +A) Using *cset bake* on the command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Access recipe file using ``cset cookbook``. +- Set required recipe inputs on command-line (or as environment variables for greater flexibility). +- Example to generate full-domain spatial maps of ``VARNAME`` for all output times: + +.. code-block:: + + cset cookbook generic_surface_spatial_plot_sequence + cset bake -i "input_data_path" -o "my_output_path" + -r generic_surface_spatial_plot_sequence + --VARNAME="temperature_at_screen_level" + --MODEL_NAME="my_model_label" + --METHOD="" + --SUBAREA_TYPE='None' --SUBAREA_EXTENT='None' --SUBAREA_NAME='None' + [-s STYLE_FILE] [--plot-resolution PLOT_RESOLUTION] [--skip-write] + + +B) Configuring the *cset_workflow* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Update workflow configuration settings via ``rose edit`` GUI or in ``rose-suite.conf`` file. +- Complete ``General setup options`` and ``Cycling and Model options`` details - see :doc:`/usage/workflow-configure`. +- Set required configuration options on ``Diagnostics / Surface (2D) fields`` panel: + +:: + + SURFACE_FIELDS = ['temperature_at_screen_level', , ...] + SPATIAL_SURFACE_FIELD = True + + +C) Example python code +^^^^^^^^^^^^^^^^^^^^^^ +""" + +import CSET.operators.plot as cset_plot +import CSET.operators.read as cset_read + +# Set path to input data +file_path = "../../../../../../tests/test_data/air_temperature_global.nc" + +# Read selected variable(s) of interest +cube = cset_read.read_cube(file_path, ["temperature_at_screen_level"]) + +# Plot single example frame using spatial_pcolormesh_plot +cset_plot.spatial_pcolormesh_plot(cube) diff --git a/docs/source/reference/cset_gallery/index.rst b/docs/source/reference/cset_gallery/index.rst new file mode 100644 index 000000000..d0a00e828 --- /dev/null +++ b/docs/source/reference/cset_gallery/index.rst @@ -0,0 +1,14 @@ +:orphan: + + +CSET Gallery +============ + +This gallery demonstrates example CSET functionality. + +The gallery is divided into sections as described below. All entries show the code used to produce the example plot. Additionally there are links to download the code directly as source or as part of a jupyter notebook, these links are at the bottom of the page. + +.. toctree:: + :maxdepth: 1 + + generated/index diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index 642b8314c..e63eae3f5 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -8,6 +8,7 @@ components. :maxdepth: 1 glossary + cset_gallery/index cli recipe-format operators diff --git a/pyproject.toml b/pyproject.toml index 4e169090c..4a967eb74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,6 +87,7 @@ src = ["src", "test"] [tool.ruff.lint] extend-select = ["B", "D", "I"] +per-file-ignores = {"docs/source/reference/cset_gallery/examples/**/*.py" = ["D"]} [tool.ruff.lint.pydocstyle] convention = "numpy" diff --git a/src/CSET/operators/plot.py b/src/CSET/operators/plot.py index a8a72a51e..ad23fd07d 100644 --- a/src/CSET/operators/plot.py +++ b/src/CSET/operators/plot.py @@ -21,6 +21,7 @@ import logging import math import os +import sys from typing import Literal import cartopy.crs as ccrs @@ -74,6 +75,11 @@ ############################ +def in_sphinx_gallery(): + """Test if running plot code in sphinx-gallery context.""" + return "sphinx_gallery" in sys.modules + + def _append_to_plot_index(plot_index: list) -> list: """Add plots into the plot index, returning the complete plot index.""" with open("meta.json", "r+t", encoding="UTF-8") as fp: @@ -773,9 +779,10 @@ def _plot_and_save_spatial_plot( logging.debug("Set colorbar ticks and labels.") # Save plot. - fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - logging.info("Saved spatial plot to %s", filename) - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved spatial plot to %s", filename) + plt.close(fig) def _plot_and_save_postage_stamp_spatial_plot( @@ -1140,9 +1147,10 @@ def _plot_and_save_line_power_spectrum_series( ax.legend(handles=handles, loc="best", ncol=1, frameon=True, fontsize=16) # Save plot. - fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - logging.info("Saved line plot to %s", filename) - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved line plot to %s", filename) + plt.close(fig) def _plot_and_save_vertical_line_series( @@ -1926,7 +1934,7 @@ def spatial_multi_pcolormesh_plot( overlay_cube: iris.cube.Cube | None = None, contour_cube: iris.cube.Cube | None = None, point_cube: iris.cube.Cube | None = None, - filename: str = None, + filename: str | None = None, sequence_coordinate: str = "time", stamp_coordinate: str = "realization", **kwargs, @@ -2945,7 +2953,7 @@ def _plot_and_save_postage_stamp_power_spectrum_series( stamp_coordinate: str, filename: str, title: str, - series_coordinate: str = None, + series_coordinate: str | None = None, **kwargs, ): """Plot and save postage (ensemble members) stamps for a power spectrum series. From dd90ef7f091ad33c2013931df9744aee17ea3485 Mon Sep 17 00:00:00 2001 From: Jenna Thornton Date: Mon, 29 Jun 2026 12:27:33 +0100 Subject: [PATCH 2/9] Added a histogram example --- .../examples/line/plot_line_histogram.py | 65 +++++++++++++++++++ docs/source/reference/cset_gallery/index.rst | 2 + src/CSET/operators/plot.py | 7 +- 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 docs/source/reference/cset_gallery/examples/line/plot_line_histogram.py diff --git a/docs/source/reference/cset_gallery/examples/line/plot_line_histogram.py b/docs/source/reference/cset_gallery/examples/line/plot_line_histogram.py new file mode 100644 index 000000000..7f69061a0 --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/line/plot_line_histogram.py @@ -0,0 +1,65 @@ +""" +Histogram plot +============== + +Generate histogram of region-averaged field. + +Line are generated using either CSET operators :py:mod:`CSET.operators.plot.plot_histogram_series`. + +General functionality is provided using :doc:`CSET recipe ` ``generic_surface_histogram_series.yaml`` + + +A) Using *cset bake* on the command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Access recipe file using ``cset cookbook``. +- Set required recipe inputs on command-line (or as environment variables for greater flexibility). +- Use ``SEQUENCE="realization"`` to generate one histogram for all times. +- Use ``SEQUENCE="time"`` for a histogram at each output time. +- Example to generate full domain histogram of ``VARNAME`` for all output times: + +.. code-block:: + + cset cookbook generic_surface_histogram_series + cset bake -i "input_data_path" ["input_data_path2" "input_data_path3" "..."] -o "my_output_path" + -r generic_surface_histogram_series.yaml + --VARNAME="temperature_at_screen_level" + --MODEL_NAME="my_model_label" "my_model_label2" "my_model_label3" "..." + --SEQUENCE="realization" + --SUBAREA_TYPE='None' --SUBAREA_EXTENT='None' --SUBAREA_NAME='None' + [-s STYLE_FILE] [--plot-resolution PLOT_RESOLUTION] [--skip-write] + + + +B) Configuring the *cset_workflow* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Update workflow configuration settings via ``rose edit`` GUI or in ``rose-suite.conf`` file. +- Complete ``General setup options`` and ``Cycling and Model options`` details - see :doc:`/usage/workflow-configure`. +- Set required configuration options on ``Diagnostics / Surface (2D) fields`` panel. +- Set ``HISTOGRAM_SURFACE_FIELD_SEQUENCE=False`` to generate one histogram for all times. +- Set ``HISTOGRAM_SURFACE_FIELD_SEQUENCE=True`` for a histogram at each output time. + +:: + + SURFACE_FIELDS = ['temperature_at_screen_level', , ...] + HISTOGRAM_SURFACE_FIELD = True + HISTOGRAM_SURFACE_FIELD_SEQUENCE = False + + + +C) Example python code +^^^^^^^^^^^^^^^^^^^^^^ +""" + +import CSET.operators.plot as cset_plot +import CSET.operators.read as cset_read + +# Set path to input data +file_paths = "../../../../../../tests/test_data/air_temperature_global.nc" + +# Read selected variable(s) of interest +cubes = cset_read.read_cubes(file_paths, ["temperature_at_screen_level"]) + +# Plot domain histogram +cset_plot.plot_histogram_series(cubes, sequence_coordinate="realization") diff --git a/docs/source/reference/cset_gallery/index.rst b/docs/source/reference/cset_gallery/index.rst index d0a00e828..898800ffb 100644 --- a/docs/source/reference/cset_gallery/index.rst +++ b/docs/source/reference/cset_gallery/index.rst @@ -8,6 +8,8 @@ This gallery demonstrates example CSET functionality. The gallery is divided into sections as described below. All entries show the code used to produce the example plot. Additionally there are links to download the code directly as source or as part of a jupyter notebook, these links are at the bottom of the page. +Examples use test_data provided with CSET release. + .. toctree:: :maxdepth: 1 diff --git a/src/CSET/operators/plot.py b/src/CSET/operators/plot.py index ad23fd07d..ff5bdc143 100644 --- a/src/CSET/operators/plot.py +++ b/src/CSET/operators/plot.py @@ -1599,9 +1599,10 @@ def _plot_and_save_histogram_series( ax.legend(loc="best", ncol=1, frameon=True, fontsize=16) # Save plot. - fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - logging.info("Saved histogram plot to %s", filename) - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved histogram plot to %s", filename) + plt.close(fig) def _plot_and_save_postage_stamp_histogram_series( From 84516150be9fefb0e6ae30e60f8780ee833f4752 Mon Sep 17 00:00:00 2001 From: mo-nmakryg Date: Mon, 29 Jun 2026 15:21:02 +0100 Subject: [PATCH 3/9] Add extra line to line timeseries plot --- .../examples/line/plot_line_timeseries.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/source/reference/cset_gallery/examples/line/plot_line_timeseries.py b/docs/source/reference/cset_gallery/examples/line/plot_line_timeseries.py index faa243e5b..40f695d6b 100644 --- a/docs/source/reference/cset_gallery/examples/line/plot_line_timeseries.py +++ b/docs/source/reference/cset_gallery/examples/line/plot_line_timeseries.py @@ -19,8 +19,8 @@ .. code-block:: cset cookbook generic_surface_domain_mean_time_series - cset bake -i "input_data_path" ["input_data_path2" "input_data_path3" "..."] -o "my_output_path" - -r generic_surface_domain_mean_time_series + cset -v bake -i "input_data_path" ["input_data_path2" "input_data_path3" "..."] -o "my_output_path" + -r generic_surface_domain_mean_time_series.yaml --VARNAME="temperature_at_screen_level" --MODEL_NAME="my_model_label" "my_model_label2" "my_model_label3" "..." --SUBAREA_TYPE='None' --SUBAREA_EXTENT='None' --SUBAREA_NAME='None' @@ -49,10 +49,15 @@ import CSET.operators.read as cset_read # Set path to input data -file_paths = "../../../../../../tests/test_data/long_forecast_air_temp_fcst_1.nc" +file_paths = [ + "../../../../../../tests/test_data/long_forecast_air_temp_fcst_1.nc", + "../../../../../../tests/test_data/long_forecast_air_temp_fcst_a.nc", +] # Read selected variable(s) of interest -cubes = cset_read.read_cubes(file_paths, ["temperature_at_screen_level"]) +cubes = cset_read.read_cubes( + file_paths, ["temperature_at_screen_level"], model_names=["model_1", "model_a"] +) # Collapse input data over selected dimensions collapsed_cubes = cset_collapse.collapse( From ab405f3b24f7693dc7b05e2634cba1e39f21aaa2 Mon Sep 17 00:00:00 2001 From: mo-nmakryg Date: Mon, 29 Jun 2026 15:22:54 +0100 Subject: [PATCH 4/9] Add dataset file --- .../test_data/long_forecast_air_temp_fcst_a.nc | Bin 0 -> 22534 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/test_data/long_forecast_air_temp_fcst_a.nc diff --git a/tests/test_data/long_forecast_air_temp_fcst_a.nc b/tests/test_data/long_forecast_air_temp_fcst_a.nc new file mode 100644 index 0000000000000000000000000000000000000000..2ff9846d4fd1820ed3a69ff2dd005c424fb6496c GIT binary patch literal 22534 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 literal 0 HcmV?d00001 From 2535d2fd25bc2a3d7f94c1c4b2ee96d36e3d7009 Mon Sep 17 00:00:00 2001 From: Jenna Thornton Date: Mon, 29 Jun 2026 15:47:32 +0100 Subject: [PATCH 5/9] Added a power spectrum plot --- .../examples/line/plot_line_power_spectra.py | 63 +++++++++++++++++++ src/CSET/operators/plot.py | 15 ++--- 2 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 docs/source/reference/cset_gallery/examples/line/plot_line_power_spectra.py diff --git a/docs/source/reference/cset_gallery/examples/line/plot_line_power_spectra.py b/docs/source/reference/cset_gallery/examples/line/plot_line_power_spectra.py new file mode 100644 index 000000000..1cb4e30ad --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/line/plot_line_power_spectra.py @@ -0,0 +1,63 @@ +""" +Power spectra plot +================== + +Generate power spectra of region-averaged field. + +Line are generated using either CSET operators :py:mod:`CSET.operators.plot.plot_power_spectrum_series`. + +General functionality is provided using :doc:`CSET recipe ` ``generic_surface_power_spectrum_series.yaml`` + + +A) Using *cset bake* on the command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Access recipe file using ``cset cookbook``. +- Set required recipe inputs on command-line (or as environment variables for greater flexibility). +- Use ``SEQUENCE="realization"`` to generate one plot for all times. +- Use ``SEQUENCE="time"`` for a plot at each output time. +- Example to generate full-domain power spectra of ``VARNAME`` for all output times: + +.. code-block:: + + cset cookbook generic_surface_domain_mean_time_series + cset bake -i "input_data_path" ["input_data_path2" "input_data_path3" "..."] -o "my_output_path" + -r generic_surface_domain_mean_time_series + --VARNAME="temperature_at_screen_level" + --MODEL_NAME="my_model_label" "my_model_label2" "my_model_label3" "..." + --SEQUENCE="realization" + --SUBAREA_TYPE='None' --SUBAREA_EXTENT='None' --SUBAREA_NAME='None' + [-s STYLE_FILE] [--plot-resolution PLOT_RESOLUTION] [--skip-write] + + +B) Configuring the *cset_workflow* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Update workflow configuration settings via ``rose edit`` GUI or in ``rose-suite.conf`` file. +- Complete ``General setup options`` and ``Cycling and Model options`` details - see :doc:`/usage/workflow-configure`. +- Set required configuration options on ``Diagnostics / Surface (2D) fields`` panel. +- Set ``SPECTRUM_SURFACE_FIELD_SEQUENCE=False`` to generate spectrum for all times. +- Set ``SPECTRUM_SURFACE_FIELD_SEQUENCE=True`` for spectrum at each output time. + +:: + + SURFACE_FIELDS = ['temperature_at_screen_level', , ...] + SPECTRUM_SURFACE_FIELD = True + SPECTRUM_SURFACE_FIELD_SEQUENCE = False + + +C) Example python code +^^^^^^^^^^^^^^^^^^^^^^ +""" + +import CSET.operators.plot as cset_plot +import CSET.operators.read as cset_read + +# Set path to input data +file_paths = "../../../../../../tests/test_data/air_temperature_global.nc" + +# Read selected variable(s) of interest +cubes = cset_read.read_cubes(file_paths, ["temperature_at_screen_level"]) + +# Plot domain power spectrum +cset_plot.plot_power_spectrum_series(cubes, sequence_coordinate="realization") diff --git a/src/CSET/operators/plot.py b/src/CSET/operators/plot.py index ff5bdc143..84aaee407 100644 --- a/src/CSET/operators/plot.py +++ b/src/CSET/operators/plot.py @@ -3074,9 +3074,10 @@ def _plot_and_save_postage_stamp_power_spectrum_series( ax = plt.gca() ax.set_title(f"Member #{member.coord(stamp_coordinate).points[0]}") - fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - logging.info("Saved histogram postage stamp plot to %s", filename) - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved power spectrum histogram plot to %s", filename) + plt.close(fig) def _plot_and_save_postage_stamps_in_single_plot_power_spectrum_series( @@ -3205,7 +3206,7 @@ def _plot_and_save_postage_stamps_in_single_plot_power_spectrum_series( ax.set_title(title, fontsize=16) # Save the figure to a file - plt.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - - # Close the figure - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved power spectrum plot to %s", filename) + plt.close(fig) From 3b6eec08102e65997c401d5c265ce2e823c1b282 Mon Sep 17 00:00:00 2001 From: Richard W Jones Date: Mon, 29 Jun 2026 16:24:51 +0100 Subject: [PATCH 6/9] Add vertical profile plot example and update plot --- .../examples/line/plot_line_vertical.py | 63 +++++++++++++++++++ src/CSET/operators/plot.py | 44 +++++++------ 2 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 docs/source/reference/cset_gallery/examples/line/plot_line_vertical.py diff --git a/docs/source/reference/cset_gallery/examples/line/plot_line_vertical.py b/docs/source/reference/cset_gallery/examples/line/plot_line_vertical.py new file mode 100644 index 000000000..07bdf9020 --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/line/plot_line_vertical.py @@ -0,0 +1,63 @@ +""" +Vertical profile plot +===================== + +Generate vertical profile of region-averaged field. + +Line are generated using either CSET operators :py:mod:`CSET.operators.plot.plot_vertical_line_series`. + +General functionality is provided using :doc:`CSET recipe ` ``generic_level_domain_mean_vertical_profile_series.yaml`` + + +A) Using *cset bake* on the command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Access recipe file using ``cset cookbook``. +- Set required recipe inputs on command-line (or as environment variables for greater flexibility). +- Example to generate domain mean vertical profiles of ``VARNAME`` for all output times: + +.. code-block:: + + cset cookbook generic_level_domain_mean_vertical_profile_series + cset -v bake -i "input_data_path" ["input_data_path2" "input_data_path3" "..."] -o "my_output_path" + -r generic_level_domain_mean_vertical_profile_series + --VARNAME="air_temperature" + --MODEL_NAME="my_model_label" "my_model_label2" "my_model_label3" "..." + --LEVELTYPE="pressure" + --SUBAREA_TYPE='None' --SUBAREA_EXTENT='None' --SUBAREA_NAME='None' + [-s STYLE_FILE] [--plot-resolution PLOT_RESOLUTION] [--skip-write] + + +B) Configuring the *cset_workflow* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Update workflow configuration settings via ``rose edit`` GUI or in ``rose-suite.conf`` file. +- Complete ``General setup options`` and ``Cycling and Model options`` details - see :doc:`/usage/workflow-configure`. +- Set required configuration options on ``Diagnostics / Surface (2D) fields`` panel: + +:: + + PRESSURE_LEVEL_FIELDS = ['air_temperature', , ...] + PRESSURE_LEVELS = ['1000','850', , ...] + PROFILE_PLEVEL = True + + +C) Example python code +^^^^^^^^^^^^^^^^^^^^^^ +""" + +import CSET.operators.collapse as cset_collapse +import CSET.operators.plot as cset_plot +import CSET.operators.read as cset_read + +# Set path to input data +file_paths = "../../../../../../tests/test_data/transect_out_umpl.nc" + +# Read selected variable(s) of interest +cubes = cset_read.read_cubes(file_paths, ["air_temperature"]) + +# Collapse input data over selected dimensions +collapsed_cubes = cset_collapse.collapse(cubes, ["longitude", "time"], "MEAN") + +# Plot domain mean time series +cset_plot.plot_vertical_line_series(collapsed_cubes, series_coordinate="pressure") diff --git a/src/CSET/operators/plot.py b/src/CSET/operators/plot.py index 84aaee407..31ca8b647 100644 --- a/src/CSET/operators/plot.py +++ b/src/CSET/operators/plot.py @@ -905,10 +905,10 @@ def _plot_and_save_postage_stamp_spatial_plot( # Overall figure title. fig.suptitle(title, fontsize=16) - - fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - logging.info("Saved contour postage stamp plot to %s", filename) - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved contour postage stamp plot to %s", filename) + plt.close(fig) def _plot_and_save_line_series( @@ -1022,9 +1022,10 @@ def _plot_and_save_line_series( ax.legend(handles=handles, loc="best", ncol=1, frameon=True, fontsize=16) # Save plot. - fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - logging.info("Saved line plot to %s", filename) - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved line plot to %s", filename) + plt.close(fig) def _plot_and_save_line_power_spectrum_series( @@ -1290,9 +1291,10 @@ def _plot_and_save_vertical_line_series( ax.legend(handles=handles, loc="best", ncol=1, frameon=True, fontsize=16) # Save plot. - fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - logging.info("Saved line plot to %s", filename) - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved line plot to %s", filename) + plt.close(fig) def _plot_and_save_scatter_plot( @@ -1364,9 +1366,10 @@ def _plot_and_save_scatter_plot( ax.autoscale() # Save plot. - fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - logging.info("Saved scatter plot to %s", filename) - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved scatter plot to %s", filename) + plt.close(fig) def _plot_and_save_vector_plot( @@ -1477,9 +1480,10 @@ def _plot_and_save_vector_plot( iplt.quiver(cube_u[::step, ::step], cube_v[::step, ::step], pivot="middle") # Save plot. - fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - logging.info("Saved vector plot to %s", filename) - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved vector plot to %s", filename) + plt.close(fig) def _plot_and_save_histogram_series( @@ -1659,10 +1663,10 @@ def _plot_and_save_postage_stamp_histogram_series( # Overall figure title. fig.suptitle(title, fontsize=16) - - fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) - logging.info("Saved histogram postage stamp plot to %s", filename) - plt.close(fig) + if not in_sphinx_gallery(): + fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution()) + logging.info("Saved histogram postage stamp plot to %s", filename) + plt.close(fig) def _plot_and_save_postage_stamps_in_single_plot_histogram_series( From 5560f4910abd52b1cd6e7a03f6d0b54ee488c30c Mon Sep 17 00:00:00 2001 From: ukmo-huw-lewis Date: Mon, 29 Jun 2026 18:36:10 +0100 Subject: [PATCH 7/9] Update following spectra changes and merge conflicts --- .../examples/line/plot_line_power_spectra.py | 18 ++++++++++++------ tests/conftest.py | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/source/reference/cset_gallery/examples/line/plot_line_power_spectra.py b/docs/source/reference/cset_gallery/examples/line/plot_line_power_spectra.py index 1cb4e30ad..5dd21c493 100644 --- a/docs/source/reference/cset_gallery/examples/line/plot_line_power_spectra.py +++ b/docs/source/reference/cset_gallery/examples/line/plot_line_power_spectra.py @@ -4,7 +4,7 @@ Generate power spectra of region-averaged field. -Line are generated using either CSET operators :py:mod:`CSET.operators.plot.plot_power_spectrum_series`. +Line are generated using either CSET operators :py:mod:`CSET.operators.plot.plot_line_series`. General functionality is provided using :doc:`CSET recipe ` ``generic_surface_power_spectrum_series.yaml`` @@ -14,8 +14,8 @@ - Access recipe file using ``cset cookbook``. - Set required recipe inputs on command-line (or as environment variables for greater flexibility). -- Use ``SEQUENCE="realization"`` to generate one plot for all times. -- Use ``SEQUENCE="time"`` for a plot at each output time. +- Use ``SINGLE_PLOT=True`` to generate one plot for all times. +- Use ``SINGLE_PLOT=False`` for a plot at each output time. - Example to generate full-domain power spectra of ``VARNAME`` for all output times: .. code-block:: @@ -25,7 +25,7 @@ -r generic_surface_domain_mean_time_series --VARNAME="temperature_at_screen_level" --MODEL_NAME="my_model_label" "my_model_label2" "my_model_label3" "..." - --SEQUENCE="realization" + --SINGLE_PLOT="True" --SUBAREA_TYPE='None' --SUBAREA_EXTENT='None' --SUBAREA_NAME='None' [-s STYLE_FILE] [--plot-resolution PLOT_RESOLUTION] [--skip-write] @@ -51,6 +51,7 @@ """ import CSET.operators.plot as cset_plot +import CSET.operators.power_spectrum as cset_spectra import CSET.operators.read as cset_read # Set path to input data @@ -59,5 +60,10 @@ # Read selected variable(s) of interest cubes = cset_read.read_cubes(file_paths, ["temperature_at_screen_level"]) -# Plot domain power spectrum -cset_plot.plot_power_spectrum_series(cubes, sequence_coordinate="realization") +# Compute domain power spectrum +spectra = cset_spectra.calculate_power_spectrum(cubes) + +# Plot power spectrum as line plot +cset_plot.plot_line_series( + spectra, series_coordinate="physical_wavenumber", single_plot=True +) diff --git a/tests/conftest.py b/tests/conftest.py index 88c704a8e..1832855b0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -325,7 +325,7 @@ def long_forecast_multi_day(long_forecast_multi_day_read_only): def long_forecast_many_cubes_read_only(): """Get long_forecast_may_cubes to run tests on. It is NOT safe to modify.""" return read.read_cubes( - "tests/test_data/long_forecast_air_temp_fcst_*.nc", "air_temperature" + "tests/test_data/long_forecast_air_temp_fcst_[123].nc", "air_temperature" ) From 7cae644be35dc90032a6299bff968a365fe0412c Mon Sep 17 00:00:00 2001 From: Mike Bush Date: Mon, 29 Jun 2026 16:52:16 +0100 Subject: [PATCH 8/9] Add spatial difference plot to gallery. --- .../plot_surface_spatial_difference.py | 74 ++++++++++++++++++ tests/test_data/air_temp_a.nc | Bin 0 -> 37063 bytes 2 files changed, 74 insertions(+) create mode 100644 docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_difference.py create mode 100644 tests/test_data/air_temp_a.nc diff --git a/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_difference.py b/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_difference.py new file mode 100644 index 000000000..18d491b8c --- /dev/null +++ b/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_difference.py @@ -0,0 +1,74 @@ +""" +Regional spatial plot +===================== + +Generate spatial map of a 2D field difference (regional data example). + +Spatial maps are generated using either CSET operators :py:mod:`CSET.operators.plot.spatial_pcolormesh_plot` or :py:mod:`CSET.operators.plot.spatial_contour_plot`. + +General functionality is provided using :doc:`CSET recipe ` ``generic_surface_spatial_difference.yaml`` + + +A) Using *cset bake* on the command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Access recipe file using ``cset cookbook``. +- Set required recipe inputs on command-line (or as environment variables for greater flexibility). +- Example to generate full-domain spatial maps of ``VARNAME`` for all output times: + +.. code-block:: + + cset cookbook generic_surface_spatial_difference + cset bake -i "input_data_path_1" "input_data_path_2" -o "my_output_path" + -r generic_surface_spatial_difference + --VARNAME="air_temperature" + --BASE_MODEL="Model 1" + --OTHER_MODEL="Model 2" + --MODEL_NAME="my_model_label" + --METHOD="" + --SUBAREA_TYPE='None' --SUBAREA_EXTENT='None' --SUBAREA_NAME='None' + [-s STYLE_FILE] [--plot-resolution PLOT_RESOLUTION] [--skip-write] + +B) Configuring the *cset_workflow* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Update workflow configuration settings via ``rose edit`` GUI or in ``rose-suite.conf`` file. +- Complete ``General setup options`` and ``Cycling and Model options`` details - see :doc:`/usage/workflow-configure`. +- Set required configuration options on ``Diagnostics / Surface (2D) fields`` panel: + +:: + + SURFACE_FIELDS = ['air_temperature', , ...] + SPATIAL_DIFFERENCE_SURFACE_FIELD = True + + +C) Example python code +^^^^^^^^^^^^^^^^^^^^^^ +""" + +import CSET.operators.constraints as cset_constrain +import CSET.operators.misc as cset_misc +import CSET.operators.plot as cset_plot +import CSET.operators.read as cset_read + +# Set path to input data +file_path = [ + "/home/users/mike.bush/repos/CSET/tests/test_data/air_temp.nc", + "/home/users/mike.bush/repos/CSET//tests/test_data/air_temp_a.nc", +] + +# Generating constraints that Iris can understand that can be used later in the code +constraint_nomethods = cset_constrain.generate_cell_methods_constraint([]) +constraint_var = cset_constrain.generate_var_constraint("air_temperature") +both_constraints = cset_constrain.combine_constraints( + constraint=constraint_nomethods, additional_constraint_1=constraint_var +) + +# Read selected variable(s) of interest +cubes = cset_read.read_cubes(file_path, constraint=both_constraints) + +# +diff = cset_misc.difference(cubes) + +# Plot single example frame (final time output only, using spatial_contour_plot) +cset_plot.spatial_contour_plot(diff) diff --git a/tests/test_data/air_temp_a.nc b/tests/test_data/air_temp_a.nc new file mode 100644 index 0000000000000000000000000000000000000000..ed51349bce64d8849f79fde8e1bec36494cac0e9 GIT binary patch literal 37063 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 literal 0 HcmV?d00001 From 9f0ddeaab89bd35746d931ab19081bf99208fef0 Mon Sep 17 00:00:00 2001 From: Mike Bush Date: Tue, 30 Jun 2026 16:51:06 +0100 Subject: [PATCH 9/9] Update to spatial difference plot in gallery. --- .../plot_surface_spatial_difference.py | 7 ++++--- tests/test_data/air_temp_a.nc | Bin 37063 -> 38108 bytes 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_difference.py b/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_difference.py index 18d491b8c..f1a756b9b 100644 --- a/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_difference.py +++ b/docs/source/reference/cset_gallery/examples/spatial/plot_surface_spatial_difference.py @@ -1,9 +1,10 @@ """ -Regional spatial plot -===================== +Spatial difference plot +======================= Generate spatial map of a 2D field difference (regional data example). +Differences are calculated using the CSET operator :py:mod:`CSET.operators.misc.difference`. Spatial maps are generated using either CSET operators :py:mod:`CSET.operators.plot.spatial_pcolormesh_plot` or :py:mod:`CSET.operators.plot.spatial_contour_plot`. General functionality is provided using :doc:`CSET recipe ` ``generic_surface_spatial_difference.yaml`` @@ -71,4 +72,4 @@ diff = cset_misc.difference(cubes) # Plot single example frame (final time output only, using spatial_contour_plot) -cset_plot.spatial_contour_plot(diff) +cset_plot.spatial_contour_plot(diff[-1]) diff --git a/tests/test_data/air_temp_a.nc b/tests/test_data/air_temp_a.nc index ed51349bce64d8849f79fde8e1bec36494cac0e9..3b22c4739361203f156476c9c7875c927b523914 100644 GIT binary patch literal 38108 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