-
Notifications
You must be signed in to change notification settings - Fork 34
Description
What happened?
Overview
The colors on the difference plots appear inverted on the Y-axis for specific lat_lon_land plots.
Source: zppy testing
Example:
Related Dependency
cartopy=0.25.0 introduces a change that results in the Y-axis being inverted (or the colors). This happens with when calculating difference (test-ref) then passing 1D X/Y data ax.contourf(). Masked data at the bottom/top of the difference data might also be related
Exact Cause
- Create a minimum reproducible example using the array data
- Figure out what changed between cartopy versions and what we need to do in e3sm_diags.
- This cartopy commit introduces the issue: SciTools/cartopy@eb4d042
I found the exact commit that introduces this regression and opened a related issue in the Cartopy repo here: SciTools/cartopy#2620.
Temporary Workaround
In PR #1021, we've temporarily pinned cartopy=<0.25.0 to prevent this the issue from occurring.
(before pinning, after pinning).
What did you expect to happen? Are there are possible answers you came across?
The diff plots should remain the same between cartopy=0.24.0 and cartopy=0.25.0.
Attempted Fixes (Not Used)
- Update
_add_contour_plot()to pass X and Y as a 2D meshgrid and settransform_first=True-- fixes the difference plots forlat_lon_land, but introduces a ton of issues with other plots here - Sorting the Y-axis with Xarray using
ascending=Falsefixes the plot, but this should not be done because it will affect other plots too.
Minimal Reproducible Example (MVCE)
Note: The data includes masked values at the grid edge.
This script below was adapted based on how e3sm_diags produces plots.
Details
"""A minimum, self-contained example demonstrating Cartopy contourf() plotting
differences between cartopy 0.24.0 and 0.25.0.
Issue: The contourf() plot appears inverted (flipped vertically) on the Y-axis
when using cartopy 0.25.0 compared to cartopy 0.24.0
Cause:
- Commit https://github.com/SciTools/cartopy/commit/eb4d0423ee9940067fa18c71cb38e178471ed888
introduces changes to "FIX: create a single inverted polygon when no exteriors found".
- This commit is where the issue first appears (11/4/2024).
Variables:
- x: longitude (0.5 o 360.5)
- y: latitude (-89.5 to 89.5)
- var: variable on (y, x) grid with masked values present at the bottom edge.
Workaround:
- Converting x and y to 2D meshgrid using numpy.meshgrid() and using transform_first=True.
- However, this workaround causes issues with many other plots.
Setup Environment 1 (Without Plot Issues):
----------------------------------------------------------
- Commit: https://github.com/SciTools/cartopy/commit/5c4ef99f7093f43e387b975391dd77179a8df9ac
- 11/3/2024: DOC: add deprecation note for clip_path [skip actions]
- Hash: 5c4ef99f7093f43e387b975391dd77179a8df9ac
Commands:
git clone https://github.com/SciTools/cartopy.git
conda create -n cartopy_5c4e4f -c conda-forge xarray=2025.12.0 netcdf4 matplotlib-base=3.10.8 ipykernel
conda activate cartopy_5c4e4f
cd cartopy
git checkout 5c4ef99f7093f43e387b975391dd77179a8df9ac
python -m pip install .
Setup Environment 2 (With Plot Issues):
----------------------------------------------------------
- Commit: https://github.com/SciTools/cartopy/commit/eb4d0423ee9940067fa18c71cb38e178471ed888
- 11/4/2024: FIX: create a single inverted polygon when no exteriors found
- Hash: eb4d0423ee9940067fa18c71cb38e178471ed888
Commands:
git clone https://github.com/SciTools/cartopy.git
conda create -n cartopy_eb4d04 -c conda-forge xarray=2025.12.0 netcdf4 matplotlib-base=3.10.8 ipykernel
conda activate cartopy_eb4d04
cd cartopy
git checkout eb4d0423ee9940067fa18c71cb38e178471ed888
python -m pip install .
"""
import urllib.request
import os
import cartopy
import matplotlib
import numpy as np
import xarray as xr
from cartopy.mpl.ticker import LatitudeFormatter, LongitudeFormatter
matplotlib.use("Agg")
import matplotlib.pyplot as plt # isort:skip # noqa: E402
# Download the data
# --------------------------------------------------------------------------
# Source: /lcrc/group/e3sm/public_html/diagnostic_output/ac.tvo/tests/1026_cartopy_0250_mvce/
public_path = "https://web.lcrc.anl.gov/public/e3sm/diagnostic_output/ac.tvo/tests/1026_cartopy_0250_mvce/"
local_data_dir = "./data_cartopy_0250_mvce"
os.makedirs(local_data_dir, exist_ok=True)
def download_data():
files = ["x.nc", "y.nc", "var.nc"]
for fname in files:
url = public_path + fname
local_path = os.path.join(local_data_dir, fname)
if not os.path.exists(local_path):
print(f"Downloading {url} to {local_path} ...")
# Set a User-Agent header to avoid HTTP 403 errors
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req) as response, open(local_path, 'wb') as out_file:
out_file.write(response.read())
else:
print(f"File {local_path} already exists.")
download_data()
# Open the data
# --------------------------------------------------------------------------
x = xr.open_dataarray(os.path.join(local_data_dir, "x.nc"))
y = xr.open_dataarray(os.path.join(local_data_dir, "y.nc"))
var = xr.open_dataarray(os.path.join(local_data_dir, "var.nc"))
# Create the figure and axis with Cartopy projection
# --------------------------------------------------------------------------
fig = plt.figure(figsize=(8.5, 11.0), dpi=150)
projection = cartopy.crs.PlateCarree(central_longitude=180)
ax = fig.add_axes((0.1691, 0.1112, 0.6465, 0.2258), projection=projection)
# Set the longitude and latitude limits.
lon_west = 0
lon_east = 360
lat_south = -90
lat_north = 90
ax.set_extent([lon_west, lon_east, lat_south, lat_north], crs=projection)
# Create the contour plot
# --------------------------------------------------------------------------
transform = cartopy.crs.PlateCarree()
contour_plot = ax.contourf(
x,
y,
var,
cmap="BrBG",
transform=transform,
norm=None,
levels=None,
extend="both",
)
# Configure aspect ratio and coast lines.
# --------------------------------------------------------------------------
ax.set_aspect((lon_east - lon_west) / (2 * (lat_north - lat_south)))
ax.coastlines(lw=0.3)
# Configure x and y axes:
# --------------------------------------------------------------------------
x_ticks = np.array([0., 60., 120., 180., 240., 300., 359.5])
y_ticks = np.array([-90, -60, -30, 0, 30, 60, 90])
ax.set_xticks(x_ticks, crs=transform)
ax.set_yticks(y_ticks, crs=transform)
ax.tick_params(labelsize=8.0, direction="out", width=1)
ax.xaxis.set_ticks_position("bottom")
ax.yaxis.set_ticks_position("left")
# Add longitude and latitude formatters
lon_formatter = LongitudeFormatter(
zero_direction_label=True, number_format=".0f"
)
lat_formatter = LatitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
# Add color bar
# --------------------------------------------------------------------------
cbax_rect = (0.8326, 0.13269999999999998, 0.0326, 0.1792)
cbax = fig.add_axes(cbax_rect)
cbar = fig.colorbar(contour_plot, cax=cbax)
cbar.ax.tick_params(labelsize=9.0, length=0)
# Save the figure
# --------------------------------------------------------------------------
conda_env = os.environ.get("CONDA_DEFAULT_ENV", "unknown_env")
output_path = os.path.abspath(f"{conda_env}_mvce.png")
fig.savefig(output_path)
print(f"Saved figure at {output_path}")Relevant log output
N/AAnything else we need to know?
No response
Environment
Latest e3sm_diags with cartopy=0.25.0