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

Inconsistency with behavior of extent and origin in imshow() #8693

Closed
astrofrog opened this issue May 31, 2017 · 9 comments
Closed

Inconsistency with behavior of extent and origin in imshow() #8693

astrofrog opened this issue May 31, 2017 · 9 comments

Comments

@astrofrog
Copy link
Contributor

astrofrog commented May 31, 2017

Following a brief discussion on Gitter, I've decided to open an issue here but I'm not 100% sure if this is a bug or intended behavior. The following script compares the usage of origin and extent when the limits are fixed to the same intervals and same axis direction:

import numpy as np
import matplotlib.pyplot as plt

image = np.arange(100).reshape((10, 10))

subplot = 0

fig = plt.figure(figsize=(8, 8))

for extent in [None, [1, 9, 1, 9]]:
    for origin in ['lower', 'upper']:
        subplot += 1
        ax = fig.add_subplot(2, 2, subplot)
        ax.imshow(image, interpolation='nearest', origin=origin,
                  extent=extent)
        ax.set_xlim(0, 10)
        ax.set_ylim(0, 10)
        ax.set_title('extent={0} origin={1}'.format(extent, origin),
                     fontsize=8)

fig.savefig('thumbs.png')

The output is:

thumbs

As you can see, when no extent is specified, the origin keyword has no impact on the result whereas it does when the extent is specified.

There is either a bug here, or something that exists for historical reasons that deserves its own documentation section.

In any case, the documentation for origin doesn't make much sense:

origin : ['upper' | 'lower'], optional, default: None
    Place the [0,0] index of the array in the upper left or lower left
    corner of the axes. If None, default to rc `image.origin`.

the concept of 'upper left' or 'lower left' is tricky since the axes can be made to go in either direction, so whether [0,0] is at the upper or lower corner visually depends on the direction of the axes.

It seems that when extent is not specified, origin simply means that the y limits are flipped but this can be reverted by setting ylim. When extent is present, the array is truly flipped vertically.

cc @tacaswell @ngoldbaum @WeatherGod

@efiring
Copy link
Member

efiring commented May 31, 2017

Yes, this is about as confusing as an API plus documentation can get. I suspect that what we have to do is figure out what the code is actually doing, and then figure out how to describe that in the docstring without making the reader's head spin. I am assuming that the behavior hasn't changed any time recently, but that needs to be checked.
Note that this example involves the interaction of origin, extent, and manual setting of xlim and ylim. I think that the xlim and ylim parts are turning a moderately confusing API into a hopelessly confusing one.
Maybe we will need to come up with a new name and a new, comprehensible, API.

@ImportanceOfBeingErnest
Copy link
Member

I've often seen this happening and I actually never considered it to be an issue. To my understanding, when setting the origin to upper, the limits as well as the extent have to be inverted in y direction.

        im = ax.imshow(image, interpolation='nearest', origin=origin,extent=extent )
        ax.set_xlim(0, 10)
        if origin == "upper":
            ax.set_ylim(10, 0)
            if extent:
                im.set_extent(extent[:2] + extent[2:][::-1])
        else:
            ax.set_ylim(0,10)

Only setting the extent, but not the ylim works fine, as well as not setting the ylim but the extent. Just, if you decide to set any of them, invert the y coordinate when using origin=upper.

So I guess the sentence in the docstring could be

"If you set origin to "upper" any y coordinates you may specify using extent and/or ylim need to be reversed."

@anntzer
Copy link
Contributor

anntzer commented Jun 1, 2017

I think there is a fundamental issue with the "origin" system, in that it assumes that a there will be a single image artist in the axes that will get to decide how to pick the axes limits. We then get inconsistencies when the limits are specified (or not) using the extent kwarg at the same time, or I guess funny things can happen too if we have two images in the same axes that have different choices of origin... See also discussion at #7471 for a similar problem: artists can provide hints as to how to layout their containing axes, but they should typically not force that layout (otherwise, there can be conflicts with another artist).

Of course, I am not suggesting to get rid of the "origin" system (which is more intuitive than manually setting the ylims to (top, bottom) instead of (bottom, top)). But a possibility would be...

  1. when the AxesImage is created, the origin attribute is set normally.
  2. when Axes.add_image is called, we read the origin attribute; depending on its value, we invert or not the ylims (so the last added image indeed wins... not sure there's much that can be done about it).
  3. the origin attribute of AxesImage becomes a read-only property; before being added to an Axes, it returns the originally set value; but after being added to an Axes, it simply returns whether the Axes have inverted ylims or not.

Note that this is only somewhat related to the issue at hand, which is probably due to the following snippet:

    def get_extent(self):
        """Get the image extent: left, right, bottom, top"""
        if self._extent is not None:
            return self._extent
        else:
            sz = self.get_size()
            numrows, numcols = sz
            if self.origin == 'upper':
                return (-0.5, numcols-0.5, numrows-0.5, -0.5)
            else:
                return (-0.5, numcols-0.5, -0.5, numrows-0.5)

so origin is indeed (incorrectly, IMO) ignored when extent is specified...

@p-robot
Copy link

p-robot commented Sep 27, 2017

Related to this issue, I came across the following example when I was trying to create my own custom plots using imshow. I found this a little confusing that setting the y-limits would completely flip the image that's output.

import numpy as np
from matplotlib import pyplot as plt

imm = np.array([[0, 1, 2, 3],
[1, 1, 2, 3],
[2, 2, 2, 3],
[3, 3, 3, 3]])

fig, ax = plt.subplots(ncols = 2)
for i in range(2):
    ax[i].imshow(imm, cmap = 'hot', vmin = 0, vmax = 5)
ax[1].set_ylim([-0.5, 3.5])
plt.show()

output

@WeatherGod
Copy link
Member

WeatherGod commented Sep 27, 2017 via email

@p-robot
Copy link

p-robot commented Sep 27, 2017

Whoops, I wasn't aware set_ylim could flip the y axis.

@WeatherGod
Copy link
Member

WeatherGod commented Sep 27, 2017 via email

dfhssilva added a commit to dfhssilva/SOMPY that referenced this issue Feb 7, 2021
When lattice is rect, the blob positions need to be flipped to match
the hexagons positions.

The axis limits were inverting the axis. I think the reason might be
related to matplotlib/matplotlib#8693. This is solved in this commit.
@github-actions
Copy link

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

@github-actions github-actions bot added the status: inactive Marked by the “Stale” Github Action label Apr 12, 2023
@jklymak
Copy link
Member

jklymak commented Apr 12, 2023

We now have a whole tutorial on this. I'll close this.

@jklymak jklymak closed this as completed Apr 12, 2023
@QuLogic QuLogic removed the status: inactive Marked by the “Stale” Github Action label Apr 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants