Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Add support for images with units #27721

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

Draft
wants to merge 13 commits into
base: main
Choose a base branch
Loading
from
Draft

Conversation

dstansby
Copy link
Member

@dstansby dstansby commented Jan 30, 2024

PR summary

This PR adds machinery for the data array given to a ScalarMappable to have units, and for those units to be used with images and associated colorbars.

Fixes #25062
Fixes #17447
Fixes #19476
Fixes astropy/astropy#11306

Still needs a user facing example adding, but opening to get early feedback and full CI runs.

PR checklist

Copy link
Member

@story645 story645 left a comment

Choose a reason for hiding this comment

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

So if this is scalarmappables than that I think should be the title, but I'm a bit confused about what's happening w/ categorical here especially given scatter categorical color is a pretty big use case for extending units to scalarmappable. Not that this PR has to do that, but I'm concerned about this PR preventing it.

Comment on lines 26 to 31
def _check_axis(axis):
if axis is None:
raise units.ConversionError(
"Categorical data does not support unit conversion that is not "
"attached to an x or y Axis."
)
Copy link
Member

Choose a reason for hiding this comment

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

huh? Does this mean no categorical color? Technically, categorical should work just fine w/ NoNorm (and a colorbar where the labels are in the middle rather than the edges).

Copy link
Member Author

Choose a reason for hiding this comment

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

This means no categorical color in this PR, because I didn't dive into the error it gave me... you raise a good point though that this PR should as a minimum not prevent categorical colorbars in the future. Let me take a look again at some point later this week, and I'll ping you with what I find.

Copy link
Member

Choose a reason for hiding this comment

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

What's the error? Just curious if it would be the same issue for #27706

Copy link
Member Author

Choose a reason for hiding this comment

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

It was

RuntimeError: <matplotlib.category.StrCategoryConverter object at 0x126bcd610> failed when trying to return the default units for this image. This may be because support has not been implemented for `axis=None` in the default_units() method.

because StrCategoryConverter.default_units() actually uses the axis argument (unlike all our other conversion interfaces.

I think this is fixable though, not sure why I just put those errors in... Don't have time now, but will chase categorical support next time I come back to this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually I fixed it 🎉 . Definitely don't have time to push the fix now, but glad it works!

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
plt.imshow([["a", "b"], ["c", "d"]])
plt.colorbar()
plt.show()

Figure_1

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, wondering if we can make a formatter for this, something gets to: (b/c is also useful for any of the discrete cases)

fig, ax = plt.subplots()
im = ax.imshow(([[0,1,2], [2, 0, 1], [1, 2, 0]]), cmap=mcolors.ListedColormap(["tab:orange", "tab:blue", "tab:green"], 3))
cb = fig.colorbar(im)
cb.set_ticks([1/3, 1, 1+2/3])
cb.set_ticklabels([0,1,2])

Untitled

Copy link
Member

@story645 story645 Jan 30, 2024

Choose a reason for hiding this comment

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

I think to do this as a legend would probably be via helper method that grabs the color->value mapping and generates the handles, something like https://matplotlib.org/devdocs/api/collections_api.html#matplotlib.collections.PathCollection.legend_elements but on ScalarMappable

(Which if you're cool w/ me branching off your PR, I may volunteer to do...)

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, this is in a good enough state (== tests passing) now if you want to branch off and play around with the colorbar for categoricals. I'm not intending to change anything there myself in this PR, but would be happy to merge something into this branch/PR if it's not too complicated.

@dstansby dstansby force-pushed the image-units branch 2 times, most recently from 273d3f7 to 574999d Compare January 30, 2024 21:42
@dstansby dstansby marked this pull request as ready for review January 31, 2024 09:24
@anntzer
Copy link
Contributor

anntzer commented Feb 2, 2024

One thing that may be nice(?) is if the unit machinery could also ensure that for categorical images, interpolation_stage is always set to "rgba". (I guess the extra coupling between may be a bit awkward, though.) Indeed, for categoricals, interpolation_stage="data" is usually completely wrong (because category 1 isn't the "mean" of category 0 and category 2). A concrete example (but I've seen similar things quite often, typically when doing image segmentation):

import skimage.data  # for sample data
# a categorical image: which channel (r/g/b) of the image is the most intense
im = skimage.data.stereo_motorcycle()[0].argmax(2)
fig, axs = plt.subplots(2)
axs[0].imshow(im); axs[1].imshow(im, interpolation_stage="rgba")

This gives
out
where the bottom image clearly shows that the most intense channel is always either "r" (0, purple) or "b" (2, yellow); interpolation_stage="data" (top) mistakently shows sometimes "g" (1) as the most intense channel because it averages 0 and 2. (Ignore the few pixels at the middle top where green is indeed the most intense channel.)

@story645
Copy link
Member

The new colormapping pipeline merged (#28658) probably means that it'd be easier to create a new PR than rebase this one, but also should I think make the actual make it support units easier.

@dstansby dstansby marked this pull request as draft March 26, 2025 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.