Skip to content

Fix plotting of quantities in WCSAxes - #20779

Open
astrofrog wants to merge 9 commits into
astropy:mainfrom
astrofrog:wcsaxes-quantity-units
Open

astrofrog wants to merge 9 commits into
astropy:mainfrom
astrofrog:wcsaxes-quantity-units

Conversation

@astrofrog

@astrofrog astrofrog commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Description

We never really supported passing quantities to e.g. ax.scatter or ax.plot but as described in #20777, quantities were being silently ignored. I realized that we could actually make this work by leveraging the ability for Matplotlib to have hooks for unit conversion on the axes.

With this PR, several things happen:

  • Axes have a unit converter set on them so that quantities are always converted to the native units of the WCS. Note that technically speaking there is not necessarily a 1-to-1 mapping from xaxis/yaxis to world coordinates, but it doesn't matter because the idea is that the xaxis/yaxis converters really mean converters that apply to the first and second arguments of e.g. scatter and plot.
  • We no longer hard-code the input units for transforms returned by get_transform as being in degrees when passing a celestial frame, so we properly recognize e.g. arcsec input
  • The SphericalCircle and Quadrangle patches have been updated to also recognize unit converters on the axes, as they don't go through the unit conversion machinery automatically
  • Bare values passed with a celestial Frame transform are assumed to be in the WCS units instead of always degrees
  • A quantity in units which is incompatible with the WCS will now error (rather than silently being accepted before)

Things that haven't changed:

  • Bare values with no transform are still pixels
  • Quantities in u.pix are still pixels
  • plot_coord, scatter_coord and coordinate grid overlays are unchanged

This needs to be carefully tested with sunpy, ndcube, and dkist at least - @hayesla @ricardogando

@alasdairwilson - you might want to check this since it changes a little the behavior after #20600. The following:

  wcs = WCS(naxis=2, preserve_units=True)
  wcs.wcs.ctype = ["RA---TAN", "DEC--TAN"]
  wcs.wcs.cunit = ["arcsec", "arcsec"]
  ...
  ax = fig.add_subplot(projection=wcs)

  ax.plot(10.1, 20.1, "o", transform=ax.get_transform("icrs"))

will now assume that the bare values are in arcsec, not in degrees (basically bare values, when using a celestial transform, are assumed to be in the WCS units.

Because this is not a trivial fix and might break some corner cases, we should not backport it (nor is it easy to go through deprecation)

Fixes #20777

AI Disclosure

If AI tools were used to develop this pull request, describe the tools including specific model and version, how they were used, and what content is AI generated. Otherwise enter "N/A".

Assisted by Claude Opus 5.5

  • I certify that I am human and that I take full responsibility for this pull request including all interactions with reviewers.

Merge method

  • By checking this box, the PR author has requested that maintainers do NOT use the "Squash and Merge" button. Maintainers should respect this when possible; however, the final decision is at the discretion of the maintainer that merges the PR.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

@astrofrog astrofrog added the API change PRs and issues that change an existing API, possibly requiring a deprecation period label Nov 5, 2026
@astrofrog
astrofrog requested a review from Cadair November 5, 2026 10:22

@alasdairwilson alasdairwilson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is good...really good.

It breaks loads of our stuff but in a good way I think... we will have to work around it but like I said this is so much better. ❤️

@alasdairwilson

Copy link
Copy Markdown
Contributor

I think the general strategy on our side to have code that works both pre and post this PR being released.

is to change where we are doing stuff like:

transform = axes.get_transform(frame)
Tx = coord.spherical.lon.to_value(u.deg)
Ty = coord.spherical.lat.to_value(u.deg)

to

transform = axes.get_transform("world")
Tx = coord.spherical.lon.to_value(axes.wcs.world_axis_units[0])
Ty = coord.spherical.lat.to_value(axes.wcs.world_axis_units[1])

so if we are on pre #20779 astropy then we are fine because wcs units are assumed and the unit is dropped and post is of course fine because we are sending a Quantity anyway.

Because this is not a trivial fix and might break some corner cases, we should not backport it (nor is it easy to go through deprecation)

Yeah... part of the problem when we were discussing #20777 yesterday was that we used code that assumed degrees and it was fine because it WAS degrees and we realised when writing sunpy/sunpy#8819 and related branches in that stack PR that astropy was now assuming wcs units....

We can fix all our internal code in the manner I described above, the problem is, as you point out, that we taught people by example to do this conversion to u.deg and we can't undo that teaching so those people were gonna be bitten by our 8.1 release anyway. So the good news is then that even though more user code will break due to this...it hasn't made the user-facing problem much worse for us, they were already going to have problems if they were passing degrees to maps that are now in arcsec and at least this way if they update astropy and as long as they are passing the values as a Quantity, they might not even notice.

@pllim pllim added this to the v8.1.0 milestone Nov 5, 2026

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I wondered a bit about astropy.visualization.quantity_support, but now see that you are using some of its functionality internally. So, in a way, you just turn it on by default, which makes sense.

Inline a small comment, and a small query.

Comment thread astropy/visualization/wcsaxes/core.py Outdated
def convert(val, unit, axis):
# Pixel coordinates are plotted with the default transform, which does
# not use the world units
if isinstance(val, u.Quantity) and val.unit == u.pix:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably very unlikely to happen in practice, but someone might put in a quantity in "kpix", so perhaps it is worth doing val.unit.is_equivalent(u.pix) and val.to_value(u.pix) instead?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread astropy/visualization/wcsaxes/core.py Outdated
return MplQuantityConverter.convert(val, unit, axis)


# A single instance, since Matplotlib refuses to replace an explicitly set

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that be considered a bug in matplotlib? Worth reporting?

Is it why you cannot use MplQuantityConverter more directly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a bug, I think it's just by design

@astrofrog
astrofrog force-pushed the wcsaxes-quantity-units branch from 8af16f7 to e7dbb8a Compare November 13, 2026 11:24
@astrofrog

Copy link
Copy Markdown
Member Author

@alasdairwilson @Cadair - as we are a week away from feature freeze, I'd like us to make a decision on this. I was wondering if you could confirm if sunpy is happy with this and we can go ahead or whether it will be too disruptive?

This branch has not been deployed

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

Labels

API change PRs and issues that change an existing API, possibly requiring a deprecation period Docs visualization.wcsaxes visualization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

4 participants