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

imshow RGB plot overlaps map borders when using projection #3687

Closed
bask0 opened this issue Jan 11, 2020 · 5 comments
Closed

imshow RGB plot overlaps map borders when using projection #3687

bask0 opened this issue Jan 11, 2020 · 5 comments

Comments

@bask0
Copy link

bask0 commented Jan 11, 2020

xarry version: '0.14.0'
matplotlib version: '3.1.1'

When I create an RGB plot using ds.plot.imshow with a procection, the plot overlaps the map borders:

import cartopy.crs as ccrs
import xarray
import matplotlib.pyplot as plt

ds = xr.load_dataset(...)

ax = plt.subplot(projection=ccrs.Robinson())
(ds.isel(time=[0, 4, 8])).plot.imshow(robust=True, ax=ax, transform=ccrs.PlateCarree())

RGB plot

When using ds.plot.imshow with a single layer, the plot looks as expected:

import cartopy.crs as ccrs
import xarray
import matplotlib.pyplot as plt

ds = xr.load_dataset(...)

ax = plt.subplot(projection=ccrs.Robinson())
(ds.isel(time=0)).plot.imshow(robust=True, ax=ax, transform=ccrs.PlateCarree(), add_colorbar=False)

single layer plot

@dcherian
Copy link
Contributor

Does this work if you use matplotlib directly? i.e. plt.plot(ds.isel(time=[0, 4, 8]).values, ...)

@bask0
Copy link
Author

bask0 commented Jan 13, 2020

Does this work if you use matplotlib directly? i.e. plt.plot(ds.isel(time=[0, 4, 8]).values, ...)

@dcherian Thanks, yes, this works as expected:

ax = plt.subplot(projection=ccrs.Robinson())
ax.imshow(ds.isel(time=[0, 4, 8]).transpose('lat', 'lon', 'time').values[-1::-1, :, :], transform=ccrs.PlateCarree())

rgb map

@dcherian
Copy link
Contributor

Thanks, I'm marking this as a bug.

If you have the time, it would be nice if you could look into this and send in a PR.

We do some RGB stuff here:

xarray/xarray/plot/plot.py

Lines 632 to 642 in 8a650a1

imshow_rgb = plotfunc.__name__ == "imshow" and darray.ndim == (
3 + (row is not None) + (col is not None)
)
if imshow_rgb:
# Don't add a colorbar when showing an image with explicit colors
add_colorbar = False
# Matplotlib does not support normalising RGB data, so do it here.
# See eg. https://github.com/matplotlib/matplotlib/pull/10220
if robust or vmax is not None or vmin is not None:
darray = _rescale_imshow_rgb(darray, vmin, vmax, robust)
vmin, vmax, robust = None, None, False

and imshow is here:

xarray/xarray/plot/plot.py

Lines 843 to 915 in 8a650a1

@_plot2d
def imshow(x, y, z, ax, **kwargs):
"""
Image plot of 2d DataArray using matplotlib.pyplot
Wraps :func:`matplotlib:matplotlib.pyplot.imshow`
While other plot methods require the DataArray to be strictly
two-dimensional, ``imshow`` also accepts a 3D array where some
dimension can be interpreted as RGB or RGBA color channels and
allows this dimension to be specified via the kwarg ``rgb=``.
Unlike matplotlib, Xarray can apply ``vmin`` and ``vmax`` to RGB or RGBA
data, by applying a single scaling factor and offset to all bands.
Passing ``robust=True`` infers ``vmin`` and ``vmax``
:ref:`in the usual way <robust-plotting>`.
.. note::
This function needs uniformly spaced coordinates to
properly label the axes. Call DataArray.plot() to check.
The pixels are centered on the coordinates values. Ie, if the coordinate
value is 3.2 then the pixels for those coordinates will be centered on 3.2.
"""
if x.ndim != 1 or y.ndim != 1:
raise ValueError(
"imshow requires 1D coordinates, try using " "pcolormesh or contour(f)"
)
# Centering the pixels- Assumes uniform spacing
try:
xstep = (x[1] - x[0]) / 2.0
except IndexError:
# Arbitrary default value, similar to matplotlib behaviour
xstep = 0.1
try:
ystep = (y[1] - y[0]) / 2.0
except IndexError:
ystep = 0.1
left, right = x[0] - xstep, x[-1] + xstep
bottom, top = y[-1] + ystep, y[0] - ystep
defaults = {"origin": "upper", "interpolation": "nearest"}
if not hasattr(ax, "projection"):
# not for cartopy geoaxes
defaults["aspect"] = "auto"
# Allow user to override these defaults
defaults.update(kwargs)
if defaults["origin"] == "upper":
defaults["extent"] = [left, right, bottom, top]
else:
defaults["extent"] = [left, right, top, bottom]
if z.ndim == 3:
# matplotlib imshow uses black for missing data, but Xarray makes
# missing data transparent. We therefore add an alpha channel if
# there isn't one, and set it to transparent where data is masked.
if z.shape[-1] == 3:
alpha = np.ma.ones(z.shape[:2] + (1,), dtype=z.dtype)
if np.issubdtype(z.dtype, np.integer):
alpha *= 255
z = np.ma.concatenate((z, alpha), axis=2)
else:
z = z.copy()
z[np.any(z.mask, axis=-1), -1] = 0
primitive = ax.imshow(z, **defaults)
return primitive

@bask0
Copy link
Author

bask0 commented Jan 14, 2020

Thanks, I'll give it a try

@mathause
Copy link
Collaborator

This happens in matplotlib when you pass an RGBA array. Thus the following should cause the same problem.

ax = plt.subplot(projection=ccrs.Robinson())
ax.imshow(ds.isel(time=[0, 1, 4, 8]).transpose('lat', 'lon', 'time').values[-1::-1, :, :], transform=ccrs.PlateCarree())

I.e. it is an matplotlib or cartopy error. However, xarray always adds an alpha channel internally, see

xarray/xarray/plot/plot.py

Lines 901 to 903 in 8a650a1

# matplotlib imshow uses black for missing data, but Xarray makes
# missing data transparent. We therefore add an alpha channel if
# there isn't one, and set it to transparent where data is masked.

@bask0 bask0 closed this as completed Jan 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants