Skip to content

WCSAxes silently ignores the units of a Quantity plotted with a world transform #20777

Open

Description

@hayesla

Description

I've been working with plotting points on a WCSAxes with sunpy maps, as part of our work migrating sunpy.map.Map to be based on ndcube, and it seems that plotting a Quantity on WCSAxes with transform=ax.get_transform("world") silently drops its unit. The bare number is then read in the WCS's world units, so the marker can end up in the wrong place with no warning or error.

So like here,

ax.plot(100 * u.deg, 10 * u.deg, "o", transform=ax.get_transform("world"))
ax.plot(100 * u.arcsec, 10 * u.arcsec, "o", transform=ax.get_transform("world"))

these two calls draw markers at the same position. On a default WCS, where the world units are degrees, the arcsec marker is plotted at 10 degrees (which is the case for sunpy maps as the wcs is in degrees).

for example:

import sunpy.map 
import sunpy.data.sample
from astropy import units as u 
aia = sunpy.map.Map(sunpy.data.sample.AIA_171_IMAGE)

fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(projection=aia)
aia.plot(axes=ax, clip_interval=(1, 99.9) * u.percent)
ax.plot(10*u.deg, 10*u.deg, transform=ax.get_transform("world"), marker='o', color='r', ms=10, label="(100, 100) degrees")
ax.plot(10*u.arcsec, 10*u.arcsec, transform=ax.get_transform("world"), marker='o', color='k', label="(100, 100) arcsec")

ax.legend(loc="lower right", fontsize=8)
Image

so you can see its plotting both as 10 degrees and ignoring the quantities.

this is because:

aia.wcs.world_axis_units
['deg', 'deg']

as far as I can tell, this is why it happens:

  1. Matplotlib has no converter registered for Quantity by default, so it keeps only the values.
  2. The world transform receives plain floats and assumes they are in wcs.world_axis_units.

quantity_support() doesn't help, matplotlib's unit conversion works per axis and doesn't know about the transform, so values are converted to the first unit plotted on the axis rather than to the WCS units. The result is only correct if that first unit happens to match the WCS.

plot_coord with a SkyCoord gives the right position. scatter, errorbar and fill drop the unit in the same way as plot.

also the overlays page doesnt mention any of this. It shows ax.scatter([34], [3.2], transform=ax.get_transform('world')) with plain numbers, but doesn't say that the numbers must be in the WCS's world units or that a Quantity would lose its unit. It also doesn't mention world_axis_units, or plot_coord as the safe alternative. The same page passes a Quantity with a transform to Quadrangle, which does convert units, so it's reasonable to expect plot and scatter to do the same.

Expected behavior

I guess a Quantity should either be plotted where its value and unit say it is, or raise an error? It should not be silently reinterpreted.

If possible i'd suggest that WCSAxes converts a Quantity to the transform's input units before calling matplotlib, and raises UnitConversionError for incompatible units such as metres on an angle axis?

WCSAxes is the only place that knows both the data's unit and what the transform expects. raising an error for any Quantity would also be an improvement over the current behaviour, if that is preferred.

Versions

import astropy
astropy.system_info()
platform
--------
platform.platform() = 'macOS-15.7.3-arm64-arm-64bit-Mach-O'
platform.version() = 'Darwin Kernel Version 24.6.0: Wed Nov  5 21:30:44 PST 2025; root:xnu-11417.140.69.705.2~1/RELEASE_ARM64_T6041'
platform.python_version() = '3.15.2'

packages
--------
astropy              8.0.2
numpy                2.5.4
scipy                1.18.1
matplotlib           3.11.2
pandas               3.1.1
pyerfa               2.0.1.5

Activity

  1. self-assigned this
    on Nov 5, 2026
  2. astrofrog commented on Nov 5, 2026

    @astrofrog
    Member

    Thanks for reporting this - I have a fix which I'll open tomorrow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions