Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add new Psyplot diagnostic #2653

Merged
merged 8 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.. _api.esmvaltool.diag_scripts.psyplot_diag:

Psyplot Diagnostic
==================

.. automodule:: esmvaltool.diag_scripts.psyplot_diag
:no-members:
:no-inherited-members:
:no-show-inheritance:
8 changes: 0 additions & 8 deletions doc/sphinx/source/api/esmvaltool.diag_scripts.rst

This file was deleted.

16 changes: 14 additions & 2 deletions doc/sphinx/source/api/esmvaltool.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@ ESMValTool is mostly used as a command line tool. However, it is also possible
to use (parts of) ESMValTool as a library. This section documents the public
API of ESMValTool.


Shared Diagnostic Code
----------------------

.. toctree::
:maxdepth: 1

esmvaltool.diag_scripts.shared
esmvaltool.diag_scripts
esmvaltool.diag_scripts.ocean


Diagnostic Scripts
------------------

.. toctree::
:maxdepth: 1

esmvaltool.diag_scripts.emergent_constraints
esmvaltool.diag_scripts.mlr
esmvaltool.diag_scripts.ocean
esmvaltool.diag_scripts.psyplot_diag
3 changes: 2 additions & 1 deletion doc/sphinx/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,14 @@

autodoc_mock_imports = [
'cartopy',
'cftime',
'cf_units',
'cftime',
schlunma marked this conversation as resolved.
Show resolved Hide resolved
'ESMPy',
'esmvalcore',
'GDAL',
'iris',
'psutil',
'psyplot',
'rasterio',
'scipy',
'sklearn',
Expand Down
7 changes: 6 additions & 1 deletion doc/sphinx/source/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,9 @@ Can ESMValTool plot arbitrary model output?

Recipe :ref:`recipe_monitor` allows for the plotting of any preprocessed model.
The plotting parameters are set through a yaml configuration file, and the
type of plots to be generated are determined in the recipe.
type of plots to be generated are determined in the recipe.

Moreover, recipe :ref:`recipes_psyplot_diag` and the corresponding diagnostic
:ref:`psyplot_diag.py <api.esmvaltool.diag_scripts.psyplot_diag>` provide a
high-level interface to the `Psyplot <https://psyplot.github.io/>`__ package
which can be used to create a large variety of different plots.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions doc/sphinx/source/recipes/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,18 @@ Other
.. toctree::
:maxdepth: 1

recipe_examples
recipe_capacity_factor
recipe_cmorizers
recipe_ensclus
recipe_esacci_lst
recipe_examples
recipe_monitor
recipe_multimodel_products
recipe_rainfarm
recipe_psyplot
recipe_pv_capacity_factor
recipe_seaice_feedback
recipe_rainfarm
recipe_seaice
recipe_seaice_drift
recipe_seaice_feedback
recipe_shapeselect
recipe_toymodel
64 changes: 64 additions & 0 deletions doc/sphinx/source/recipes/recipe_psyplot.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
.. _recipes_psyplot_diag:

Psyplot Diagnostics
===================

Overview
--------

These recipes showcase the use of the Psyplot diagnostic that provides a
high-level interface to `Psyplot <https://psyplot.github.io/>`__ for ESMValTool
recipes.


Available recipes and diagnostics
---------------------------------

Recipes are stored in recipes/

* recipe_psyplot.yml

Diagnostics are stored in diag_scripts/

* :ref:`psyplot_diag.py <api.esmvaltool.diag_scripts.psyplot_diag>`


Variables
---------

Arbitrary variables are supported.


Observations and reformat scripts
---------------------------------

Arbitrary datasets are supported.


References
----------

* Sommer, (2017), The psyplot interactive visualization framework, Journal of
Open Source Software, 2(16), 363, doi:10.21105/joss.00363


Example plots
-------------

.. _fig_psyplot_1:
.. figure:: /recipes/figures/psyplot/psyplot_CanESM5.jpg
:align: center
:width: 50%

Historical near-surface air temperature climatology over Europe simulated by
CanESM5 between 1995 and 2014. The plot visualizes the invidividual
rectangular grid cells of the model's regular grid.

.. _fig_psyplot_2:
.. figure:: /recipes/figures/psyplot/psyplot_ICON-ESM-LR.jpg
:align: center
:width: 50%

Historical near-surface air temperature climatology over Europe simulated by
ICON-ESM-LR between 1995 and 2014. The plot visualizes the invidividual
triangular grid cells of the model's unstructured grid.
4 changes: 4 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ dependencies:
- imagemagick
- julia
- nco
- psyplot
- psy-maps
- psy-reg
- psy-simple
- python-cdo
- rasterio
- ruamel.yaml
Expand Down
4 changes: 4 additions & 0 deletions environment_osx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ dependencies:
- cdo
- imagemagick
- nco
- psy-maps
- psy-reg
- psy-simple
- psyplot
- python-cdo
- rasterio
- ruamel.yaml
Expand Down
7 changes: 4 additions & 3 deletions esmvaltool/config-references.yml
Original file line number Diff line number Diff line change
Expand Up @@ -679,21 +679,22 @@ projects:
cmip6dicad: BMBF CMIP6 Project Germany
cmug: ESA CMUG
crescendo: EU H2020 project CRESCENDO
dlrveu: DLR project VEU
dlrveu2: DLR project VEU2
dlrveu: DLR project VEU
embrace: EU FP7 project EMBRACE
esm2025: EU H2020 project ESM2025 - Earth system models for the future
esmval: DLR project ESMVal
eucp: EU H2020 European Climate prediction
eval4cmip: DLR and University of Bremen project funded by the Initiative and Networking Fund of the Helmholtz Society
ewatercycle: eWaterCycle project
ipcc_ar6: IPCC AR6 WG1 contributions
isenes3: EU H2020 Infrastructure for the European Network for Earth System Modelling - Phase 3
primavera: EU H2020 project PRIMAVERA
qa4ecv: QA4ECV
russell_project: US Clivar, Ocean Carbon Biogeochemistry, ESGF, NOAA, NSF, NASA, US Antarctic program
trr181: DFG Project TRR-181, Energy transfers in Atmosphere and Ocean
ukesm: UKESM, UK Earth System Model project (NERC)
usmile: ERC Synergy Grant USMILE
isenes3: EU H2020 Infrastructure for the European Network for Earth System Modelling - Phase 3
eucp: EU H2020 European Climate prediction


realms:
Expand Down
143 changes: 143 additions & 0 deletions esmvaltool/diag_scripts/psyplot_diag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Create arbitrary Psyplot plots.

Description
-----------
This diagnostic provides a high-level interface to Psyplot.

Author
------
Manuel Schlund (DLR, Germany)

Notes
-----
For each input dataset, an individual plot is created. This diagnostic supports
arbitrary variables of arbitrary datasets.

Configuration options in recipe
-------------------------------
psyplot_func: str
Function used to plot the data. Must be a function of
:mod:`psyplot.project.plot`. Run ``python -c "from psyplot.project import
plot; print(plot.show_plot_methods())"`` to get a list of all currently
supported plotting functions (make sure to run this command in your
ESMValTool environment).
psyplot_kwargs: dict, optional
Optional keyword arguments for the plotting function given by
``psyplot_func``. String arguments can include facets in curly brackets
which will be derived from the corresponding dataset, e.g., ``clabel:
'{long_name} [{units}]'``, ``title: '{long_name} Climatology of {dataset}
({start_year}-{end_year})'``.
savefig_kwargs: dict, optional
Optional keyword arguments for :func:`matplotlib.pyplot.savefig`. By
default, uses ``bbox_inches: tight, dpi: 300, orientation: landscape``.
seaborn_settings: dict, optional
Options for :func:`seaborn.set` (affects all plots).

"""
import logging
from contextlib import redirect_stdout
from copy import deepcopy
from io import StringIO
from pathlib import Path
from pprint import pformat

import matplotlib.pyplot as plt
import psyplot.project as psy
import seaborn as sns

from esmvaltool.diag_scripts.shared import (
ProvenanceLogger,
get_plot_filename,
run_diagnostic,
)

logger = logging.getLogger(Path(__file__).stem)


def _get_default_cfg(cfg):
"""Get default options for configuration dictionary."""
cfg = deepcopy(cfg)
cfg.setdefault('psyplot_kwargs', {})
cfg.setdefault('savefig_kwargs', {
'bbox_inches': 'tight',
'dpi': 300,
'orientation': 'landscape',
})
cfg.setdefault('seaborn_settings', {})
return cfg


def _get_plot_func(cfg):
"""Get psyplot plot function."""
if 'psyplot_func' not in cfg:
raise ValueError("Necessary option 'psyplot_func' missing")
if not hasattr(psy.plot, cfg['psyplot_func']):
with redirect_stdout(StringIO()) as str_in:
psy.plot.show_plot_methods()
all_plot_funcs = str_in.getvalue()
raise AttributeError(
f"Invalid psyplot_func '{cfg['psyplot_func']}' (must be a "
f"function of the module psyplot.project.plot). Currently "
f"supported:\n{all_plot_funcs}")
logger.info(
"Using plotting function psyplot.project.plot.%s", cfg['psyplot_func'])
return getattr(psy.plot, cfg['psyplot_func'])


def _get_psyplot_kwargs(cfg, dataset):
"""Get keyword arguments for psyplot plotting function."""
psyplot_kwargs = deepcopy(cfg['psyplot_kwargs'])
for (key, val) in psyplot_kwargs.items():
if isinstance(val, str):
try:
val = val.format(**dataset)
except KeyError as exc:
raise ValueError(
f"Not all necessary facets psyplot_kwargs '{key}: {val}' "
f"available for dataset" f"\n{pformat(dataset)}") from exc
psyplot_kwargs[key] = val
return psyplot_kwargs


def main(cfg):
"""Run diagnostic."""
cfg = _get_default_cfg(cfg)
sns.set(**cfg['seaborn_settings'])
plot_func = _get_plot_func(cfg)

# Create individual plots for each dataset
input_data = list(cfg['input_data'].values())
for dataset in input_data:
filename = dataset['filename']
logger.info("Creating plot '%s' for %s", cfg['psyplot_func'], filename)

# Create plot
psyplot_kwargs = _get_psyplot_kwargs(cfg, dataset)
plot_func(filename, **psyplot_kwargs)

# Save plot
basename = Path(filename).stem
plot_path = get_plot_filename(basename, cfg)
plt.savefig(plot_path, **cfg['savefig_kwargs'])
logger.info("Wrote %s", plot_path)
plt.close()

# Provenance tracking
caption = (f"Plot {cfg['psyplot_func']} of {dataset['long_name']} of "
f"dataset {dataset['dataset']} ({dataset['start_year']}-"
f"{dataset['end_year']}).")
provenance_record = {
'ancestors': [filename],
'authors': ['schlund_manuel'],
'caption': caption,
}
with ProvenanceLogger(cfg) as provenance_logger:
provenance_logger.log(plot_path, provenance_record)


if __name__ == '__main__':

with run_diagnostic() as config:
main(config)
Loading