Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Allow unit-ful ScalarMappable data #20962

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

Closed
wants to merge 104 commits into from
Closed

Conversation

dstansby
Copy link
Member

@dstansby dstansby commented Sep 1, 2021

PR Summary

Allows 2D data with units attached to be used by imshow. Fixes #19476, and supersedes #19481.

PR Checklist

  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (run flake8 on changed files to check).
  • New features are documented, with examples if plot related.
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).
  • Conforms to Matplotlib style conventions (install flake8-docstrings and run flake8 --docstring-convention=all).
  • New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).

tacaswell and others added 20 commits August 28, 2021 13:35
Fixes matplotlib#20516

PGF's `random steps` decoration seems to be the most similar,
but does not exactly match the behaviour described in matplotlib's docs.
Therefore I repurposed the `randomness` argument as a seed to give
control on how the line looks afterwards.
Co-authored-by: Elliott Sales de Andrade <[email protected]>
Switch documented deprecations in mathtext by `__getattr__` deprecations
Co-authored-by: Tim Hoffmann <[email protected]>
…_output

API: rename draw_no_output to draw_without_rendering
Make warning for no-handles legend more explicit.
Remove unused HostAxes._get_legend_handles.
- parse() already normalizes prop=None to prop=FontProperties().
- We don't need to explicitly attach a canvas to a plain Figure() before
  calling savefig() anymore; FigureCanvasBase is always attached and
  handles the dispatching to the correct concrete canvas class.
@@ -733,6 +735,31 @@ def set_data(self, A):
self._rgbacache = None
self.stale = True

def _convert_units(self, A):
# Take the first element since units expects a 1D sequence, not 2D
converter = munits.registry.get_converter(A[0])
Copy link
Member

Choose a reason for hiding this comment

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

We probably want to stash the converter the first time through?

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense. It does raise the question of making converter immutable once it has been set once (ie. the first time set_data() is called). Should this be the case, or should we provide a method to manually change the converter after a set_data() call?

@tacaswell
Copy link
Member

This required fewer changes than I had naively expected :)

@jklymak
Copy link
Member

jklymak commented Oct 4, 2021

If I pass a mappable with units "mega-joules" and another to the same axes with units "giga-joules" and they use the same norm, what happens? I would expect they would be converted to the same units, and plotted comparably. I don't think that happens here... If that doesn't happen, then I'm not clear what I am gaining from unit support, and I should just strip the bare array.

@jklymak jklymak added the status: needs comment/discussion needs consensus on next step label Oct 4, 2021
@dstansby
Copy link
Member Author

dstansby commented Oct 4, 2021

Good point - can you write some (pseudo-)code that does what you're describing? More specifically how you're adding those two mappables to the Axes, and how you're getting them to share a norm (or a colorbar). I may be wrong, but I don't think we have the concept of two mappables sharing a norm or colorbar at the moment?

@jklymak
Copy link
Member

jklymak commented Oct 4, 2021

The point is that if they are not sharing the norm or colorbar, what is the advantage of dealing with the units versus just stripping the values out of the container? Just formatting the colorbar?

@dstansby
Copy link
Member Author

dstansby commented Oct 4, 2021

Yes, I think that's correct.

@jklymak
Copy link
Member

jklymak commented Oct 5, 2021

Currently we use units to format axises for datetime and categorical. Are there other uses? I can maybe see it being nice to format a colorbar with datetime, though I am skeptical. What are the other use cases you envision for this?

@dstansby
Copy link
Member Author

Why are you skeptical? Is it not a good new feature to have colorbar ticks automatically formatted and located for data with units?

@jklymak
Copy link
Member

jklymak commented Oct 11, 2021

I am skeptical because units do not currently work as advertised for univariate axes, and here they only work in the restricted sense of formatting the axes with no design for unit conversion of the scalars. I am also concerned because the new ability of colorbars to behave like other axes is very new, and this will definitely stress the new behaviour.

If you look on Stackoverflow, unit conversion, particularly for categoricals (but datetime is a close second), is one of the leading sources of confusion. I think we should be very cautious about adding to the unit API.

@dstansby
Copy link
Member Author

I am skeptical because units do not currently work as advertised for univariate axes

Can you expand a bit on this? I'm not sure I follow

here they only work in the restricted sense of formatting the axes with no design for unit conversion of the scalars.

As I said above, I don't think this is an issue because we don't have the concept of two mappables sharing a norm or colorbar at the moment. Do you want to block this PR on adding support for two mappables sharing a colorbar?

@jklymak
Copy link
Member

jklymak commented Oct 11, 2021

I am skeptical because units do not currently work as advertised for univariate axes

Can you expand a bit on this? I'm not sure I follow

The original goal was that you could pass (104, 'cm') and (72, 'inches') to an axis, set its units to ('m') and the data would be plotted at 1.04 and 1.83. There is an example to this effect and it does not work. The dream was also that you could eventually change the axis units to 'cubits' and that would also convert the data.

Fixing this was supposed to be something done with the CZI support, but I'm not sure if the work ended up going that way.

here they only work in the restricted sense of formatting the axes with no design for unit conversion of the scalars.

As I said above, I don't think this is an issue because we don't have the concept of two mappables sharing a norm or colorbar at the moment. Do you want to block this PR on adding support for two mappables sharing a colorbar?

I often have one colorbar representing many mappables, usually in different axes. They don't "share" the colorbar in code, because we don't do that, but they do share the colorbar conceptually.

@dstansby
Copy link
Member Author

The original goal was that you could pass (104, 'cm') and (72, 'inches') to an axis, set its units to ('m') and the data would be plotted at 1.04 and 1.83. There is an example to this effect and it does not work.

It seems to work for me (albeit with an external units library):

import astropy.units as u
from astropy.visualization import quantity_support
import numpy as np
import matplotlib.pyplot as plt
quantity_support()

x = np.arange(10)
y_rad = np.linspace(0, 2 * np.pi, 10) * u.rad
y_deg = np.linspace(0, 180, 10) * u.deg

fig, ax = plt.subplots()
ax.scatter(x, y_rad)
ax.scatter(x, y_deg)
# ax.xaxis.set_units(u.deg)  # This doesn't work
plt.show()

Changing the units does not though...

I often have one colorbar representing many mappables, usually in different axes. They don't "share" the colorbar in code, because we don't do that, but they do share the colorbar conceptually.

Right, but you have to manually make sure that all your data is comparable and have to manually ensure they're plotted with the same norm and colormap, and then add a colorbar from a single mappable, so something like:

norm = Norm(vmin=vmin, vmax=vmax)
cmap = cmap

im1 = ax1.imshow(data1, norm=norm, cmap=cmap)
im2 = ax2.imshow(data2, norm=norm, cmap=cmap)
fig.colorbar(im2)

im1 has nothing to do with the colorbar (in the code), but you just so happen to have scaled it so it makes sense with the colorbar. It's the same as plotting two sets of data on twinned Axes and then making sure that the scale and limits of both twinned Axes are the same.

As it stands with this PR the behaviour is exactly the same, ie. you would have to manually make sure multiple scalarmappables are scaled correctly for one colorbar. I think what you're getting at (please correct me if I'm wrong!) is having an API like:

cbar = fig.colobar(norm=Norm(vmin, vmax), cmap=cmap)
im1 = ax1.imshow(data1, colobar=cbar)
im2 = ax2.imshow(data2, colobar=cbar)

and units/vmin/vmax being taken into account by the colorbar correctly? I think this would be an awesome API, and am +/-0 blocking this PR on such an API existing (if it's desirable).

So I'm happy to put this PR on hold, but I think it is a nice new feature, and want to be sure there's good reasons for putting it on hold.

@jklymak
Copy link
Member

jklymak commented Oct 11, 2021

Don't put on hold just because of my skepticism! I think we were hoping to discuss last week during the meeting. I imagine we can take a look at it this week if you want a definitive decision. Happy to wait for a week when you can attend, or maybe some other core devs are interested in weighing in on the feature's behalf.

@dstansby
Copy link
Member Author

There's definitely no rush - I'll try and come along to the meeting this week though, thanks for the heads up!

@tacaswell
Copy link
Member

This PR is affected by a re-writing of our history to remove a large number of accidentally committed files see discourse for details.

To recover this PR it will need be rebased onto the new default branch (main). There are several ways to accomplish this, but we recommend (assuming that you call the matplotlib/matplotlib remote "upstream"

git remote update
git checkout main
git merge --ff-only upstream/main
git checkout YOUR_BRANCH
git rebase --onto=main upstream/old_master
# git rebase -i main # if you prefer
git push --force-with-lease   # assuming you are tracking your branch

If you do not feel comfortable doing this or need any help please reach out to any of the Matplotlib developers. We can either help you with the process or do it for you.

Thank you for your contributions to Matplotlib and sorry for the inconvenience.

@jklymak
Copy link
Member

jklymak commented Nov 8, 2021

@dstansby let us know if/when you can make a meeting to discuss this. If you can't make it, let us know and we can try and discuss without you

@jklymak jklymak marked this pull request as draft November 29, 2021 07:50
@jklymak
Copy link
Member

jklymak commented Nov 29, 2021

@dstansby this needs a rebase so I moved to drafts....

@dstansby
Copy link
Member Author

@jklymak thanks - I don't have any bandwidth to pick this back up and discuss it in the near future, so if anyone else wants to take it up feel free.

@jklymak
Copy link
Member

jklymak commented Dec 14, 2021

We discussed this on the call last week, and should discuss again. However, I will give my screed about categoricals:
Every single day on Stackoverflow there is at least one person who thinks Matplotlib is very broken because they plot 1000 numbers or dates, and get 1000 ticks because they mistakenly passed in strings for x and/or y. Every day, often numerous times a day. Then, instead of fixing their problem by converting to float or datetime, they try and mess around with the tick intervals using set_xticks or even set_major_locator, of course losing the proper spacing between the data, and churning out innumerate garbage. This is a huge usability problem that we have made by trying to be clever, because the user has no idea what is going on - I would argue that if someone is sophisticated enough to know about set_major_locator and they get caught by this, then we have made an API mistake.

My point being that I strongly feel people should have to manually opt in to categorical plotting. I actually think they should have to manually opt into any unit handling, including dates (ax.plot(x, y, xunits='dates') is not too much to ask).

For expanding categoricals into scalar mappables, I'm fine with the feature, but I think if we just do it via object inspection, folks will end up not understanding what has happened to their data, and will again think Matplotlib is broken. In my opinion this API should be accompanied by some way of manually opting in.

@dstansby dstansby closed this Dec 28, 2021
@dstansby dstansby deleted the unit-images branch December 28, 2021 15:12
@QuLogic QuLogic removed this from the v3.6.0 milestone Sep 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow use of unitful data in images