Visualization¶
xrops.viz provides plotting helpers and a family of validation
panels — Operator subclasses that return a matplotlib.figure.Figure,
so a panel slots into a Sequential or Graph pipeline as the final step.
Axes & colormaps¶
make_axes builds a (optionally cartopy-projected) axes grid; cmap_for
looks up the default colormap for a variable; shared_norm derives a
common colour normalization across panels; PRESETS is the registry of
named cartopy extents.
make_axes
¶
make_axes(projection: Any = None, *, fig: Figure | None = None, figsize: tuple[float, float] = (8, 5)) -> tuple[Figure, Any]
Create a Figure + Axes pair, applying a preset extent if requested.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
projection
|
Any
|
Preset name (one of :data: |
None
|
fig
|
Figure | None
|
Optional pre-existing Figure to draw into. When |
None
|
figsize
|
tuple[float, float]
|
Figure size when |
(8, 5)
|
Returns:
| Type | Description |
|---|---|
Figure
|
|
Any
|
class: |
tuple[Figure, Any]
|
applied. With |
Raises:
| Type | Description |
|---|---|
ImportError
|
|
Source code in packages/xrtoolz-ops/src/xrops/viz/_src/projections.py
cmap_for
¶
cmap_for(variable: str | Variable | None, default: str = 'viridis') -> str
Return the registry-recommended colormap for variable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variable
|
str | Variable | None
|
Variable instance, canonical short name (case-insensitive),
or |
required |
default
|
str
|
Fallback colormap when |
'viridis'
|
Returns:
| Type | Description |
|---|---|
str
|
Matplotlib colormap name (or any string the caller accepted on |
str
|
registration — cmocean colormaps via |
str
|
when |
Source code in packages/xrtoolz-ops/src/xrops/viz/_src/cmaps.py
shared_norm
¶
shared_norm(*arrays: DataArray | Dataset, q: tuple[float, float] | None = (0.02, 0.98), symmetric: bool = False) -> tuple[float, float]
Compute matched (vmin, vmax) across multiple inputs.
Useful for multi-panel comparison grids where the eye should not pick up colour-scale stretch artefacts as if they were structural differences in the data.
Reductions go through xarray (.quantile / .min /
.max) rather than np.quantile on a materialised
np.concatenate, so dask-backed inputs stay lazy: only the
final scalar result is realised.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*arrays
|
DataArray | Dataset
|
One or more :class: |
()
|
q
|
tuple[float, float] | None
|
|
(0.02, 0.98)
|
symmetric
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
float
|
|
float
|
|
Source code in packages/xrtoolz-ops/src/xrops/viz/_src/norm.py
Cartopy presets
The projection kwarg of SpatialMapPanel (and make_axes) accepts a
preset name from PRESETS ("global", "north_atlantic",
"gulf_stream", "kuroshio", "mediterranean"), a cartopy class name,
or an instantiated cartopy CRS. With a preset, the right set_extent is
applied automatically.
Variable → colormap registry
Default colormaps are looked up from the curated Variable registry
(SSH → RdBu_r, SST → RdYlBu_r, salinity → viridis, ice →
Blues). cmap_for performs the lookup; SpatialMapPanel(var=…) calls
it automatically when cmap is unset.
V1 — Scale & spectral skill¶
LeadTimeSkillPanel
¶
Bases: _ValidationPanel
Plot skill vs forecast lead time.
Consumes the output of
:class:xrops.metrics.SkillByLeadTime — a
:class:xr.DataArray (or :class:xr.Dataset) indexed by
lead_dim (default "lead_time").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lead_dim
|
str
|
Name of the lead-time dimension. Default
|
'lead_time'
|
ylabel
|
str
|
Y-axis label. Default |
'Skill'
|
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/scales.py
ScaleSkillPanel
¶
ScaleSkillPanel(*, region_dim: str = 'region', metric: str | None = None, ylabel: str = 'Score', **kw: Any)
Bases: _ValidationPanel
Bar chart of skill broken down by region.
Consumes :class:xrops.metrics.EvaluateByRegion output — a
:class:xr.Dataset whose data_vars are per-metric scores indexed
by a region dimension.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
region_dim
|
str
|
Name of the region dimension. Default |
'region'
|
metric
|
str | None
|
Name of the variable in the input Dataset to plot.
|
None
|
ylabel
|
str
|
Y-axis label. Default |
'Score'
|
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/scales.py
SpectralSkillPanel
¶
SpectralSkillPanel(*, freq_dim: str = 'freq', log_x: bool = True, log_y: bool = False, ylabel: str = 'PSD score', **kw: Any)
Bases: _ValidationPanel
Plot skill vs spatial frequency / wavenumber.
Consumes a 1-D PSD-score-style :class:xr.DataArray indexed by a
frequency or wavenumber coord (default "freq"). Pairs with
:class:xrops.metrics.PSDScore output collapsed to 1-D.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freq_dim
|
str
|
Frequency / wavenumber dim. Default |
'freq'
|
log_x
|
bool
|
Whether to use a log x-axis. Default |
True
|
log_y
|
bool
|
Whether to use a log y-axis. Default |
False
|
ylabel
|
str
|
Y-axis label. Default |
'PSD score'
|
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/scales.py
TimeSeriesErrorPanel
¶
TimeSeriesErrorPanel(*, time_dim: str = 'time', method_dim: str | None = 'method', metrics: Sequence[str] | None = None, ylabel: str = 'Score', legend_loc: str = 'best', palette: str | dict[str, str] | None = None, **kw: Any)
Bases: _ValidationPanel
Scalar skill metric(s) over time, one line per method.
Two input layouts are accepted:
method_dimset (default"method"): a Dataset whose data variables are metrics on(method_dim, time_dim). Each metric gets its own subplot (stacked, shared x) with one line per method.method_dim=None: a Dataset whose data variables are the methods, each 1-D ontime_dim. One axes, one line per variable.
A DataArray is treated as a single-metric Dataset. Colours come from
:func:method_palette, so a method keeps its colour across figures.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
time_dim
|
str
|
Time / step dimension. Default |
'time'
|
method_dim
|
str | None
|
Method dimension, or |
'method'
|
metrics
|
Sequence[str] | None
|
Subset of data variables to plot (metrics, or methods
when |
None
|
ylabel
|
str
|
Y-axis label for a single-metric figure. Multi-metric
figures label each subplot with its metric name instead.
Default |
'Score'
|
legend_loc
|
str
|
Matplotlib legend location. Default |
'best'
|
palette
|
str | dict[str, str] | None
|
|
None
|
Returns:
| Type | Description |
|---|---|
|
A Figure with one axes per metric. |
Raises:
| Type | Description |
|---|---|
ValueError
|
A requested metric is missing, or a series is not
1-D along |
Example
import matplotlib matplotlib.use("Agg") import numpy as np, xarray as xr from xrops.viz.validation import TimeSeriesErrorPanel ds = xr.Dataset( ... {"rmse": (("method", "time"), np.random.rand(3, 20))}, ... coords={ ... "method": ["duacs", "miost", "4dvarnet"], ... "time": np.arange(20), ... }, ... ) fig = TimeSeriesErrorPanel(ylabel="RMSE [m]")(ds) sorted(line.get_label() for line in fig.axes[0].get_lines()) ['4dvarnet', 'duacs', 'miost']
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/timeseries.py
V1.5 — PSD plots¶
Power-spectrum visualisations consuming transforms.power_spectrum and
metrics.psd_score outputs.
PSDIsotropicPanel
¶
PSDIsotropicPanel(*, freq_dim: str = 'freq_r', space_scale: float = 1.0, wavelength_label: str = 'Wavelength [units]', wavenumber_label: str = 'Wavenumber [cycles / unit]', ylabel: str = 'PSD', show_wavelength: bool = True, **kw: Any)
Bases: _PSDPanelBase
Log-log isotropic PSD vs radial wavenumber.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freq_dim
|
str
|
Radial-frequency dim name ( |
'freq_r'
|
space_scale
|
float
|
Multiplier converting |
1.0
|
wavelength_label
|
str
|
Top-axis label. Default
|
'Wavelength [units]'
|
wavenumber_label
|
str
|
Bottom-axis label. Default
|
'Wavenumber [cycles / unit]'
|
ylabel
|
str
|
Y-axis label. Default |
'PSD'
|
show_wavelength
|
bool
|
Toggle the wavelength twin axis. Default
|
True
|
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/psd.py
PSDIsotropicScorePanel
¶
PSDIsotropicScorePanel(*, freq_dim: str = 'freq_r', threshold: float = 0.5, space_scale: float = 1.0, wavelength_label: str = 'Wavelength [units]', wavenumber_label: str = 'Wavenumber [cycles / unit]', ylabel: str = 'PSD score', score_var: str = 'score', show_wavelength: bool = True, resolved_units: str | None = None, clip: bool = True, ylim: tuple[float, float] | None = None, **kw: Any)
Bases: _PSDPanelBase
PSD score vs radial wavenumber with resolved-scale marker.
Plots the 1-D PSD score (typically from
:func:xrops.metrics.psd_score with isotropic=True) on a
log-x axis, marks the score threshold with a horizontal dashed
line, and annotates the resolved wavelength where the score first
crosses threshold (computed via
:func:xrops.metrics._src.spectral.find_intercept_1D).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freq_dim
|
str
|
Frequency dim name. Default |
'freq_r'
|
threshold
|
float
|
Score threshold for the resolved-scale marker.
Default |
0.5
|
space_scale
|
float
|
As :class: |
1.0
|
wavelength_label
|
str
|
Top-axis label. |
'Wavelength [units]'
|
wavenumber_label
|
str
|
Bottom-axis label. |
'Wavenumber [cycles / unit]'
|
ylabel
|
str
|
Y-axis label. |
'PSD score'
|
score_var
|
str
|
Variable name when input is a Dataset. Default
|
'score'
|
show_wavelength
|
bool
|
Toggle the twin axis. |
True
|
resolved_units
|
str | None
|
Unit string used in the resolved-scale legend
text. |
None
|
clip
|
bool
|
When |
True
|
ylim
|
tuple[float, float] | None
|
Optional |
None
|
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/psd.py
PSDSpaceTimePanel
¶
PSDSpaceTimePanel(*, freq_space_dim: str = 'freq_lon', freq_time_dim: str = 'freq_time', space_scale: float = 1.0, time_scale: float = 1.0, wavelength_label: str = 'Wavelength [units]', period_label: str = 'Period [units]', wavenumber_label: str = 'Wavenumber [cycles / unit]', frequency_label: str = 'Frequency [cycles / unit]', cmap: str = 'RdYlBu_r', vmin: float | None = None, vmax: float | None = None, show_dual_axes: bool = True, **kw: Any)
Bases: _PSDPanelBase
2-D space-time PSD with log-norm color scale.
Renders |PSD| as a pcolormesh over
(freq_space, freq_time) with a :class:matplotlib.colors.LogNorm
colour scale, with optional twin axes converting to wavelength and
period. Non-positive cells are masked before normalisation so
LogNorm can't trip on zeros.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freq_space_dim
|
str
|
Spatial-frequency dim. Default |
'freq_lon'
|
freq_time_dim
|
str
|
Temporal-frequency dim. Default |
'freq_time'
|
space_scale
|
float
|
Multiplier converting freq units to inverse
length used by the wavelength axis. Default |
1.0
|
time_scale
|
float
|
Multiplier converting freq units to inverse time
used by the period axis. Default |
1.0
|
wavelength_label
|
str
|
Top-axis label. |
'Wavelength [units]'
|
period_label
|
str
|
Right-axis label. |
'Period [units]'
|
wavenumber_label
|
str
|
Bottom-axis label. |
'Wavenumber [cycles / unit]'
|
frequency_label
|
str
|
Left-axis label. |
'Frequency [cycles / unit]'
|
cmap
|
str
|
Colormap. Default |
'RdYlBu_r'
|
vmin
|
float | None
|
Optional log-norm lower limit. |
None
|
vmax
|
float | None
|
Optional log-norm upper limit. |
None
|
show_dual_axes
|
bool
|
Toggle wavelength + period twin axes. |
True
|
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/psd.py
PSDSpaceTimeScorePanel
¶
PSDSpaceTimeScorePanel(*, threshold: float = 0.5, score_var: str = 'score', levels: ndarray | None = None, cmap: str = 'RdYlGn', clip: bool = True, **kw: Any)
Bases: PSDSpaceTimePanel
2-D space-time PSD score with threshold contour.
Same axes layout as :class:PSDSpaceTimePanel, but renders score
(linear [0, 1]) and overlays a contour line at threshold
marking the resolved boundary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
threshold
|
float
|
Score threshold contour. Default |
0.5
|
score_var
|
str
|
Variable name when input is a Dataset. Default
|
'score'
|
levels
|
ndarray | None
|
Filled-contour levels. Default |
None
|
cmap
|
str
|
Colormap. Default |
'RdYlGn'
|
clip
|
bool
|
When |
True
|
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/psd.py
Region & rotary diagnostics¶
RegionScoreBarPanel
¶
RegionScoreBarPanel(*, metrics: Sequence[str] | None = None, region_dim: str = 'region', method_dim: str | None = 'method', horizontal: bool = False, cmap: str = 'tab10', figsize: tuple[float, float] = (8, 5), **kw: Any)
Bases: _ValidationPanel
Grouped bar chart for region-stratified metric scores.
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/regime_bars.py
RotaryPolarizationPanel
¶
RotaryPolarizationPanel(*, variable: str = 'polarization', wavenumber_dim: str = 'wavenumber', y_dim: str = 'lat', cmap: str = 'RdBu_r', vmin: float = -1.0, vmax: float = 1.0, wavelength_axis: bool = True, space_scale: float = 1.0, wavelength_label: str = 'Wavelength [km]', figsize: tuple[float, float] = (6, 8), **kw: Any)
Bases: _PSDPanelBase
Heatmap of rotary polarization over wavenumber and latitude.
Inherits the constrained-layout / per-axes title pattern from
:class:_PSDPanelBase so the optional top wavelength axis doesn't
collide with the figure title.
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/rotary.py
Spatial snapshots¶
SpatialMapPanel
¶
SpatialMapPanel(*, variable: str | None = None, time_index: int | None = 0, cmap: str | None = None, vmin: float | None = None, vmax: float | None = None, projection: Any = None, coastlines: bool = True, gridlines: bool = True, cbar_label: str = '', lon: str = 'lon', lat: str = 'lat', **kw: Any)
Bases: _ValidationPanel
Single 2-D (lat, lon) snapshot with optional cartopy projection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variable
|
str | None
|
Data-variable name when input is a Dataset. |
None
|
time_index
|
int | None
|
When the input has a |
0
|
cmap
|
str | None
|
Matplotlib colormap. |
None
|
vmin
|
float | None
|
Optional lower colour-scale limit. |
None
|
vmax
|
float | None
|
Optional upper colour-scale limit. |
None
|
projection
|
Any
|
Preset name ( |
None
|
coastlines
|
bool
|
Add :meth: |
True
|
gridlines
|
bool
|
Add :meth: |
True
|
cbar_label
|
str
|
Colorbar label. Empty string suppresses it. Default
|
''
|
lon
|
str
|
Longitude coord name. Default |
'lon'
|
lat
|
str
|
Latitude coord name. Default |
'lat'
|
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/spatial.py
HovmollerPanel
¶
HovmollerPanel(*, variable: str | None = None, time_dim: str = 'time', keep_dim: str = 'lat', time_axis: Literal['x', 'y'] = 'x', cmap: str | None = None, norm: Literal['linear', 'log'] = 'linear', vmin: float | None = None, vmax: float | None = None, **kw: Any)
Bases: _ValidationPanel
Time × spatial-axis cross-section of a field.
Every dim other than time_dim and keep_dim is averaged out
(NaN-skipping, unweighted — area-weight beforehand if averaging over
latitude matters).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variable
|
str | None
|
Data-variable name when input is a Dataset. |
None
|
time_dim
|
str
|
Time dimension. Default |
'time'
|
keep_dim
|
str
|
Spatial dimension to keep, e.g. |
'lat'
|
time_axis
|
Literal['x', 'y']
|
Put time on the |
'x'
|
cmap
|
str | None
|
Matplotlib colormap. |
None
|
norm
|
Literal['linear', 'log']
|
|
'linear'
|
vmin
|
float | None
|
Optional lower colour limit. |
None
|
vmax
|
float | None
|
Optional upper colour limit. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
An unknown |
Example
import matplotlib matplotlib.use("Agg") import numpy as np, xarray as xr from xrops.viz.validation import HovmollerPanel t, lon = np.arange(30.0), np.linspace(0.0, 350.0, 36) wave = np.sin(np.deg2rad(lon[None, :] - 5.0 * t[:, None])) da = xr.DataArray( ... wave[:, None, :] * np.ones((1, 4, 1)), ... dims=("time", "lat", "lon"), ... coords={"time": t, "lat": np.arange(4.0), "lon": lon}, ... ) fig = HovmollerPanel(keep_dim="lon", time_axis="y")(da) fig.axes[0].get_xlabel(), fig.axes[0].get_ylabel() ('lon', 'time')
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/hovmoller.py
Cartesian model output¶
Idealised f/β-plane runs have x/y in metres and no lon/lat.
CartesianMapPanel draws them on equal-aspect km axes with a zero-centred
1/99 % colour scale and no cartopy; EnergyTimeSeriesPanel tracks model
invariants over time. Both compose with the wrappers below:
from xrops.viz.validation import AnimatePanel, CartesianMapPanel, EnergyTimeSeriesPanel
CartesianMapPanel(variable="zeta", contour="psi")(snapshots)
AnimatePanel(CartesianMapPanel(variable="zeta"))(snapshots) # FuncAnimation
EnergyTimeSeriesPanel(["kinetic_energy", "enstrophy"], relative=True)(invariants)
CartesianMapPanel
¶
CartesianMapPanel(*, variable: str | None = None, dims: tuple[str, str] = ('x', 'y'), units: Literal['km', 'm'] = 'km', time_index: int | None = 0, cmap: str | None = None, norm: NormSpec = 'symmetric_q99', mask: str | None = 'mask', contour: str | None = None, contour_levels: int = 10, cbar_label: str | None = None, **kw: Any)
Bases: _ValidationPanel
Single 2-D (y, x) snapshot on Cartesian axes, without cartopy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variable
|
str | None
|
Data-variable name when input is a Dataset. |
None
|
dims
|
tuple[str, str]
|
|
('x', 'y')
|
units
|
Literal['km', 'm']
|
Axis units to display, |
'km'
|
time_index
|
int | None
|
When the input has a |
0
|
cmap
|
str | None
|
Matplotlib colormap. |
None
|
norm
|
NormSpec
|
Colour limits. |
'symmetric_q99'
|
mask
|
str | None
|
Name of a mask variable or coordinate (non-zero / |
'mask'
|
contour
|
str | None
|
Name of a Dataset variable to overlay as black contour
lines (e.g. |
None
|
contour_levels
|
int
|
Number of contour levels. Default |
10
|
cbar_label
|
str | None
|
Colorbar label. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
class: |
Example
import matplotlib matplotlib.use("Agg") import numpy as np, xarray as xr from xrops.viz.validation import CartesianMapPanel x = np.linspace(0.0, 1e6, 32) zeta = np.sin(x[None, :] / 2e5) * np.cos(x[:, None] / 2e5) ds = xr.Dataset({"zeta": (("y", "x"), zeta)}, coords={"x": x, "y": x}) fig = CartesianMapPanel(variable="zeta")(ds) fig.axes[0].get_xlabel() 'x [km]'
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/cartesian.py
EnergyTimeSeriesPanel
¶
EnergyTimeSeriesPanel(variables: Sequence[str], *, relative: bool = False, log: bool = False, time: str = 'time', **kw: Any)
Bases: _ValidationPanel
One line per scalar invariant over time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variables
|
Sequence[str]
|
Names of 1-D ( |
required |
relative
|
bool
|
Plot |
False
|
log
|
bool
|
Log-scale the y-axis. Default |
False
|
time
|
str
|
Name of the time dimension. Default |
'time'
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
A variable is missing, is not 1-D along |
Example
import matplotlib matplotlib.use("Agg") import numpy as np, xarray as xr from xrops.viz.validation import EnergyTimeSeriesPanel t = np.arange(10.0) ds = xr.Dataset( ... {"ke": ("time", 2.0 - 0.01 * t), "ens": ("time", 5.0 + 0.1 * t)}, ... coords={"time": t}, ... ) fig = EnergyTimeSeriesPanel(["ke", "ens"], relative=True)(ds) fig.axes[0].lines[0].get_ydata()[0] np.float64(1.0)
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/timeseries.py
V3 — Lagrangian / Eulerian¶
EulerianLagrangianPanel
¶
EulerianLagrangianPanel(*, eulerian_var: str = 'ssh', traj_dim: str = 'trajectory', time_dim: str = 'time', lon: str = 'lon', lat: str = 'lat', **kw: Any)
Bases: _ValidationPanel
Side-by-side Eulerian field + Lagrangian trajectory bundle.
Consumes (eulerian_ds, trajectory_ds). The trajectory Dataset
follows the V3.1 schema: dims (traj_dim, time_dim) with at
least lon, lat variables sampled along each track.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
eulerian_var
|
str
|
Variable to plot from the Eulerian Dataset. |
'ssh'
|
traj_dim
|
str
|
Trajectory dimension name in the trajectory Dataset. |
'trajectory'
|
time_dim
|
str
|
Time dimension name in the trajectory Dataset. |
'time'
|
lon
|
str
|
Longitude coordinate name in both Datasets. |
'lon'
|
lat
|
str
|
Latitude coordinate name in both Datasets. |
'lat'
|
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/lagrangian.py
V4 — Process budgets¶
ProcessBudgetPanel
¶
ProcessBudgetPanel(*, time_dim: str = 'time', reduce_dims: tuple[str, ...] = ('lat', 'lon'), **kw: Any)
Bases: _ValidationPanel
Stacked-area chart of budget components + residual line.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
time_dim
|
str
|
Time coordinate name to plot against. Default
|
'time'
|
reduce_dims
|
tuple[str, ...]
|
Spatial dims to mean-reduce before plotting.
Default |
('lat', 'lon')
|
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/budgets.py
V5 — Event verification¶
EventVerificationPanel
¶
EventVerificationPanel(*, label_var: str = 'label', lon: str = 'lon', lat: str = 'lat', use_cartopy: bool = True, **kw: Any)
Bases: _ValidationPanel
Overlay matched / unmatched events + side panel of scores.
Consumes (objects_pred, objects_ref, matches, scores) where:
objects_pred/objects_refcarry an integerlabelDataArray on(lat, lon)(0 = background, n = event id).matchesis a mapping with three iterables of (pred_id, ref_id) / pred_id / ref_id keyed by"hits","false_alarms","misses".scoresis a mapping{"POD": ..., "FAR": ..., "CSI": ..., "IoU": ...}; rendered as a side text block.
Cartopy is used when available for a coastline backdrop; otherwise a plain matplotlib axes is used.
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/events.py
Composable wrappers¶
These consume any of the panels above — or a plain
(ds, ax) -> Any callable — and multiply it. They nest, so a
per-experiment N-up movie is
AnimatePanel(FacetPanel(SpatialMapPanel(...), facet_dim="experiment"))
and the six-panel scale × (ref, study, diff) mosaic is
FacetPanel(PairwiseComparePanel(SpatialMapPanel(...)), facet_dim="scale").
Generality boundary
Only single-input, single-axes panels can be wrapped — the
wrappers hand an inner panel one sliced object and one Axes. That
covers SpatialMapPanel, the PSD panels, RegionScoreBarPanel,
RotaryPolarizationPanel and the scale-skill panels, plus any
(ds, ax) -> Any callable.
EulerianLagrangianPanel (eulerian + trajectories) and
EventVerificationPanel (four inputs across an axes pair) take more
of both and are rejected at construction with a TypeError. Plots
that own the whole figure (seaborn JointGrid, pairplot) and
non-matplotlib backends (plotly, hvplot, bokeh) are also out of
scope.
FacetPanel
¶
FacetPanel(panel: InnerPanel, *, facet_dim: str, ncols: int | None = None, nrows: int | None = None, sharex: bool = True, sharey: bool = True, sharebar: bool = False, figsize_per_panel: tuple[float, float] = (5, 4), title_format: str = '{value}', subplot_kw: dict[str, Any] | None = None, **kw: Any)
Bases: _ValidationPanel
Render any single-axes panel faceted across a categorical dim.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
panel
|
InnerPanel
|
The panel to repeat. Either a |
required |
facet_dim
|
str
|
Dimension to facet over. One cell per value. |
required |
ncols
|
int | None
|
Columns in the grid. |
None
|
nrows
|
int | None
|
Rows in the grid. |
None
|
sharex
|
bool
|
Share the x axis across cells. Default |
True
|
sharey
|
bool
|
Share the y axis across cells. Default |
True
|
sharebar
|
bool
|
Replace the inner panel's per-cell colorbars with a
single shared one spanning the grid. Requires that the inner
panel produce a mappable; when none is found a warning is
emitted and per-cell behaviour is left untouched. Default
|
False
|
figsize_per_panel
|
tuple[float, float]
|
Per-cell |
(5, 4)
|
title_format
|
str
|
Per-cell title template, formatted with |
'{value}'
|
subplot_kw
|
dict[str, Any] | None
|
Forwarded to :func: |
None
|
Returns:
| Type | Description |
|---|---|
|
A Figure with one cell per |
|
|
cells are hidden. |
Example
import matplotlib matplotlib.use("Agg") import numpy as np, xarray as xr from xrops.viz.validation import FacetPanel ds = xr.DataArray( ... np.arange(12.0).reshape(4, 3), ... dims=("run", "x"), ... coords={"run": ["a", "b", "c", "d"]}, ... ).to_dataset(name="score") def line(sub, ax): ... ax.plot(sub["score"].values) fig = FacetPanel(line, facet_dim="run").call(ds) len(fig.axes) 4
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/facet.py
seasonal_groupby
¶
seasonal_groupby(ds: Dataset | DataArray, *, time: str = 'time', reduction: str = 'mean') -> Dataset | DataArray
Reduce a continuous-time object to a four-cell season dim.
A one-liner over ds.groupby(f"{time}.season") so the seasonal
mosaic is a straight feed into FacetPanel(facet_dim="season").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset | DataArray
|
Input carrying a datetime-like |
required |
time
|
str
|
Name of that coordinate. Default |
'time'
|
reduction
|
str
|
Any groupby reduction name — |
'mean'
|
Returns:
| Type | Description |
|---|---|
Dataset | DataArray
|
The reduced object with a |
Dataset | DataArray
|
|
Dataset | DataArray
|
on the string |
Dataset | DataArray
|
( |
Dataset | DataArray
|
before spring in a mosaic, so the result is reindexed. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
import numpy as np, pandas as pd, xarray as xr from xrops.viz.validation import seasonal_groupby ds = xr.DataArray( ... np.arange(365.0), ... dims="time", ... coords={"time": pd.date_range("2020-01-01", periods=365)}, ... ).to_dataset(name="sst") int(seasonal_groupby(ds).sizes["season"]) 4
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/facet.py
PairwiseComparePanel
¶
PairwiseComparePanel(panel: InnerPanel, *, method_dim: str = 'method', diff: DiffMode = 'relative', diff_kwargs: dict[str, Any] | None = None, diff_label: str | None = None, layout: Literal['row', 'col'] = 'row', sharex: bool = True, sharey: bool = True, figsize_per_panel: tuple[float, float] = (5, 4), subplot_kw: dict[str, Any] | None = None, **kw: Any)
Bases: _ValidationPanel
Side-by-side study vs reference, with an optional diff cell.
The input carries method_dim of size 2: position 0 is the
reference / baseline, position 1 the study / candidate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
panel
|
InnerPanel
|
Inner panel to render each cell with — a
|
required |
method_dim
|
str
|
Dimension holding the two methods. Default
|
'method'
|
diff
|
DiffMode
|
|
'relative'
|
diff_kwargs
|
dict[str, Any] | None
|
Attribute overrides applied to the inner panel for
the diff cell only — typically |
None
|
diff_label
|
str | None
|
Title for the diff cell. |
None
|
layout
|
Literal['row', 'col']
|
|
'row'
|
sharex
|
bool
|
Share the x axis across cells. Default |
True
|
sharey
|
bool
|
Share the y axis across cells. Default |
True
|
figsize_per_panel
|
tuple[float, float]
|
Per-cell |
(5, 4)
|
subplot_kw
|
dict[str, Any] | None
|
Forwarded to :func: |
None
|
Returns:
| Type | Description |
|---|---|
|
A Figure of three cells (two when |
Example
import matplotlib matplotlib.use("Agg") import numpy as np, xarray as xr from xrops.viz.validation import PairwiseComparePanel ds = xr.DataArray( ... np.arange(8.0).reshape(2, 4) + 1.0, ... dims=("method", "x"), ... coords={"method": ["duacs", "miost"]}, ... ).to_dataset(name="rmse") def line(sub, ax): ... ax.plot(sub["rmse"].values) fig = PairwiseComparePanel(line).call(ds) len(fig.axes) 3
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/compare.py
SpatialDiffPanel
¶
SpatialDiffPanel(panel: _ValidationPanel | None = None, *, variable: str | None = None, time_index: int | None = 0, field_cmap: str | None = None, diff_cmap: str = 'RdBu_r', vmin: float | None = None, vmax: float | None = None, diff_symmetric: bool = True, names: tuple[str, str] = ('prediction', 'reference'), figsize_per_panel: tuple[float, float] = (5, 4), **kw: Any)
Bases: _ValidationPanel
Three-axis (pred, ref, pred − ref) snapshot panel.
Called with two inputs, panel(pred, ref), each a Dataset or
DataArray on the same grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
panel
|
_ValidationPanel | None
|
Map panel used to draw each cell. |
None
|
variable
|
str | None
|
Data-variable name when the inputs are Datasets.
|
None
|
time_index
|
int | None
|
When the inputs have a |
0
|
field_cmap
|
str | None
|
Colormap for the prediction / reference cells.
|
None
|
diff_cmap
|
str
|
Colormap for the difference cell. Default |
'RdBu_r'
|
vmin
|
float | None
|
Lower colour limit shared by prediction and reference.
|
None
|
vmax
|
float | None
|
Upper colour limit, as |
None
|
diff_symmetric
|
bool
|
Centre the difference scale on zero at its
2/98 % quantiles. |
True
|
names
|
tuple[str, str]
|
Cell titles for the two inputs. Default
|
('prediction', 'reference')
|
figsize_per_panel
|
tuple[float, float]
|
Per-cell |
(5, 4)
|
Returns:
| Type | Description |
|---|---|
|
A Figure with three map cells. |
Raises:
| Type | Description |
|---|---|
ValueError
|
The two inputs are not on the same grid. |
Example
import matplotlib matplotlib.use("Agg") import numpy as np, xarray as xr from xrops.viz.validation import SpatialDiffPanel coords = {"lat": np.linspace(30, 40, 6), "lon": np.linspace(-70, -60, 8)} ref = xr.DataArray(np.ones((6, 8)), dims=("lat", "lon"), coords=coords) fig = SpatialDiffPanel()(ref + 0.1, ref) [ax.get_title() for ax in fig.axes[:3]]['prediction', 'reference', 'prediction - reference']
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/diff.py
AnimatePanel
¶
AnimatePanel(panel: InnerPanel, *, frame_dim: str = 'time', fps: int = 24, interval_ms: int | None = None, title_format: str = '{value}', figsize: tuple[float, float] | None = None, pixelwidth: int | None = None, pixelheight: int | None = None, dpi: int = 100, subplot_kw: dict[str, Any] | None = None, blit: bool = False)
Animate any single-axes panel along a frame dimension.
Deliberately not a :class:_ValidationPanel subclass: that base
contract is "callable returning a Figure", and an animation is a
FuncAnimation. Rather than bend the contract, this is a sibling
that consumes a panel — so it composes with
:class:~xrops.viz.validation.FacetPanel and
:class:~xrops.viz.validation.PairwiseComparePanel instead of
competing with them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
panel
|
InnerPanel
|
Panel to render per frame — a |
required |
frame_dim
|
str
|
Dimension to animate over. Default |
'time'
|
fps
|
int
|
Frames per second, used when |
24
|
interval_ms
|
int | None
|
Explicit inter-frame delay in milliseconds.
|
None
|
title_format
|
str
|
Per-frame title template, formatted with |
'{value}'
|
figsize
|
tuple[float, float] | None
|
Figure size in inches. |
None
|
pixelwidth
|
int | None
|
Output width in pixels. Must be paired with
|
None
|
pixelheight
|
int | None
|
Output height in pixels. Must be paired with
|
None
|
dpi
|
int
|
Dots per inch, and the pixel/inch conversion factor. |
100
|
subplot_kw
|
dict[str, Any] | None
|
Forwarded to :func: |
None
|
blit
|
bool
|
Kept for signature compatibility; only |
False
|
Example
import matplotlib matplotlib.use("Agg") import numpy as np, xarray as xr from xrops.viz.validation import AnimatePanel ds = xr.DataArray( ... np.arange(12.0).reshape(4, 3), dims=("time", "x") ... ).to_dataset(name="ssh") def line(sub, ax): ... ax.plot(sub["ssh"].values) ani = AnimatePanel(line).call(ds) ani._save_count if hasattr(ani, "_save_count") else 4 4
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/animate.py
preview
¶
Render a single frame for inspection.
Lets you tune cmap / clim / projection / figsize without paying for a full render.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset | DataArray
|
Input carrying |
required |
frame_index
|
int
|
Which frame to draw. Default |
0
|
Returns:
| Type | Description |
|---|---|
tuple[Figure, Any]
|
The |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/animate.py
get_config
¶
Return a JSON-serialisable description of this wrapper.
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/animate.py
save_animation
¶
save_animation(ani: Any, path: str | Path, *, fps: int | None = None, progress: bool = False, overwrite_existing: bool = False, writer_kwargs: dict[str, Any] | None = None) -> Path
Write a FuncAnimation to disk, dispatching on file extension.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ani
|
Any
|
The animation to write. |
required |
path
|
str | Path
|
Destination. |
required |
fps
|
int | None
|
Frame rate to encode at. |
None
|
progress
|
bool
|
Attach a tqdm progress bar ticking once per rendered frame. A no-op with a warning when tqdm is unavailable. |
False
|
overwrite_existing
|
bool
|
Permit clobbering an existing file. Default
|
False
|
writer_kwargs
|
dict[str, Any] | None
|
Forwarded to the matplotlib writer, e.g.
|
None
|
Returns:
| Type | Description |
|---|---|
Path
|
The path written. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the extension is not one of |
FileExistsError
|
If the path exists and |
RuntimeError
|
If |
Source code in packages/xrtoolz-ops/src/xrops/viz/validation/_src/animate.py
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 | |
Palette helper¶
method_palette
¶
Return a deterministic {name: colour} mapping.
The order is determined by sorting names alphabetically (so the
same set of methods always yields the same mapping regardless of
input order), then cycling through cycle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
names
|
Iterable[str]
|
Iterable of method names. Duplicates are de-duplicated. |
required |
cycle
|
Iterable[str] | None
|
Iterable of matplotlib colour specifiers. Defaults to the
|
None
|
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
|