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

Cursor's Z coordinate display for NonUniformImage is inconsistent with image #18652

Open
eldond opened this issue Oct 4, 2020 · 14 comments · May be fixed by #19104
Open

Cursor's Z coordinate display for NonUniformImage is inconsistent with image #18652

eldond opened this issue Oct 4, 2020 · 14 comments · May be fixed by #19104

Comments

@eldond
Copy link

@eldond eldond commented Oct 4, 2020

Bug report

Bug summary

When using matplotlib.image.NonUniformImage, the coordinates of the cursor are wrong: the Z-coordinate is inconsistent with the image (it appears to be based on how imshow would display the data (flipped in Y)).
The Z values also don't change at the visible boundaries between "pixels" or cells, they change somewhere else that might be consistent with different cell alignment to the x,y values.

Code for reproduction

import numpy as np
import matplotlib
import matplotlib.pyplot as plt

ax = plt.gca()

x = np.arange(201)
y = np.arange(201)
z = x[:, np.newaxis] + y[np.newaxis, :]
obj = matplotlib.image.NonUniformImage(ax=ax)
obj.set_data(x, y, z)
ax.add_image(obj)
ax.set_xlim(x.min(), x.max())
ax.set_ylim(y.min(), y.max())

plt.show()

Actual outcome

Notice that when the cursor is at the top right (value should approach 400), the value is about 200. When the cursor is at the bottom right (value should be about 200), the value approaches 400.
image
image

Expected outcome

It should be like the actual outcome, but with the correct Z-coordinates reported: should be close to 400 at the top right and close to 200 at the bottom right.

Matplotlib version

  • Operating system: Fedora 32
  • Matplotlib version: 3.1.2 (installed via pip)
  • Matplotlib backend (print(matplotlib.get_backend())): Qt5Agg, TkAgg
  • Python version: 3.8.5
  • numpy version: 1.19.1
@jklymak
Copy link
Member

@jklymak jklymak commented Oct 4, 2020

.. .does seem a problem. But out of curiosity, why are you using NonUniformImage? That seems a corner of the API we probably will prune.

@eldond
Copy link
Author

@eldond eldond commented Oct 4, 2020

It's a general way to display data with a possibly uneven grid that should be faster than contouring methods, right? Is there a better way that I'm missing?

@jklymak
Copy link
Member

@jklymak jklymak commented Oct 4, 2020

pcolormesh is what I use. There is also pcolorfast

@eldond
Copy link
Author

@eldond eldond commented Oct 5, 2020

I'm having trouble using pcolorfast when x and y aren't uniformly spaced (it throws a ValueError, copied below). NonUniformImage seems to have a substantial speed advantage over pcolormesh, and pcolormesh doesn't give a Z value for the cursor. The three methods also don't draw the boxes in the same places, which can be seen when z doesn't have many elements. For a 5x5:
image

  • pcolormesh draws 4x4 cells when it's given a 5x5 Z-grid.
  • pcolorfast draws the first cell STARTING at the first x value (0), and the last cell ENDING at the last X-value, which is inconsistent. It also fails if x is uneven.
  • NonUniformImage uses x,y as the centers of the cells, and draws the expected number. It works consistently in terms of cell count and start/end location relative to x values. Its drawing also extends, apparently indefinitely, so if I pan, I can see more of the last boxes. I think this behavior is desirable.

I discovered another problem: NonUniformImage reports z values according to cell boundaries similar to those shown by pcolorfast. That is, the transition from [7] to [8] in the 5x5 doesn't occur where the edge appears to be in the panel that used NonUniformImage; it appears along where the corresponding line from pcolorfast would be. I couldn't see this before with the 200x200 sample.

Timing data (code below):
When x and y are generated by arange:

5001 points: NonUniformImage: 4.824e-02, pcolormesh: 1.808e+00 (37.47x), pcolorfast: 1.137e-01 (2.36x)
1001 points: NonUniformImage: 3.042e-03, pcolormesh: 7.858e-02 (25.84x), pcolorfast: 9.931e-03 (3.27x)
201 points: NonUniformImage: 5.472e-04, pcolormesh: 5.473e-03 (10.00x), pcolorfast: 7.068e-03 (12.92x)
101 points: NonUniformImage: 6.008e-04, pcolormesh: 3.406e-03 (5.67x), pcolorfast: 6.968e-03 (11.60x)

When x and y are generated by linspace and then squared so grid spacing isn't even:

5001 points: NonUniformImage: 5.510e-02, pcolormesh: 1.691e+00 (30.69x), pcolorfast: FAILS
1001 points: NonUniformImage: 4.312e-03, pcolormesh: 7.616e-02 (17.66x), pcolorfast: FAILS
201 points: NonUniformImage: 4.995e-04, pcolormesh: 4.992e-03 (9.99x), pcolorfast: FAILS
101 points: NonUniformImage: 5.476e-04, pcolormesh: 2.984e-03 (5.45x), pcolorfast: FAILS

Alright, so for fewer than a few thousand by a few thousand, I might not notice, but at the 5001x5001, level, it's > 1 sec vs 0.06 s. That might matter. On the other hand, if pcolorfast worked better (consistent mapping of cell edges to X values and able to handle nonuniform X values), it might be the best, since it's not much slower than NonUniformImage for evenly spaced data.

Test code
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import platform
import time

print(platform.python_version())
print(matplotlib.__version__)
print(matplotlib.get_backend())


fig, axs = plt.subplots(3)
use_arange = True
if use_arange:
    x = np.arange(201)
    y = np.arange(len(x))
else:
    x = np.linspace(0, 1, 201) ** 2 * 200 + 10
    y = np.linspace(0, 1, len(x)) ** 2 * 200 - 10
z = x[:, np.newaxis] + y[np.newaxis, :]
print(f'shape(x) = {np.shape(x)}, shape(y) = {np.shape(y)}, shape(z) = {np.shape(z)}')

t0 = time.time()

ax = axs[0]

obj = matplotlib.image.NonUniformImage(ax=ax)
obj.set_data(x, y, z)
ax.add_image(obj)
ax.set_xlim(x.min(), x.max())
ax.set_ylim(y.min(), y.max())

t1 = time.time()

ax = axs[1]
ax.pcolormesh(x, y, z)

t2 = time.time()

ax = axs[2]
ax.pcolorfast(x, y, z)

t3 = time.time()

print(
    f'{len(x)} points: NonUniformImage: {t1 - t0:0.3e}, '
    f'pcolormesh: {t2 - t1:0.3e} ({(t2 - t1) / (t1 - t0):0.2f}x), '
    f'pcolorfast: {t3 - t2:0.3e} ({(t3 - t2) / (t1 - t0):0.2f}x)'
)

plt.show()
ValueError produced when pcolorfast is attempted with irregular x,y data
3.8.5
3.1.2
Qt5Agg
shape(x) = (201,), shape(y) = (201,), shape(z) = (201, 201)
Traceback (most recent call last):
  File "tmp_test.py", line 41, in <module>
    ax.pcolorfast(x, y, z)
  File "/home/eldond/.local/lib/python3.8/site-packages/matplotlib/__init__.py", line 1599, in inner
    return func(ax, *map(sanitize_sequence, args), **kwargs)
  File "/home/eldond/.local/lib/python3.8/site-packages/matplotlib/axes/_axes.py", line 6396, in pcolorfast
    im = mimage.PcolorImage(self, x, y, C,
  File "/home/eldond/.local/lib/python3.8/site-packages/matplotlib/image.py", line 1105, in __init__
    self.set_data(x, y, A)
  File "/home/eldond/.local/lib/python3.8/site-packages/matplotlib/image.py", line 1165, in set_data
    raise ValueError(
ValueError: Axes don't match array shape. Got (201, 201), expected (200, 200).

@dopplershift
Copy link
Contributor

@dopplershift dopplershift commented Oct 5, 2020

@eldond When using pcolormesh you need to be passing x,y values that represent the corners of the mesh, not the center points. You can also try shading='auto'.

@eldond
Copy link
Author

@eldond eldond commented Oct 5, 2020

@dopplershift Thank you, that makes sense. I should've caught that.

Anyway, I can probably use pcolorfast, but my data would be more naturally described by Z(x, y); that is, I have x, y centers and I have to make up edges (pcolorfast doesn't accept shading='auto' or shading='nearest'). But I think it'll be okay.

When used correctly, pcolorfast isn't much slower than NonUniformImage:

5001 evenly-spaced EDGE points: NonUniformImage: 5.583e-02, pcolormesh: 1.803e+00 (32.29x), pcolorfast: 7.982e-02 (1.43x)
1001 evenly-spaced EDGE points: NonUniformImage: 2.638e-03, pcolormesh: 8.543e-02 (32.38x), pcolorfast: 3.927e-03 (1.49x)
501 evenly-spaced EDGE points: NonUniformImage: 1.764e-03, pcolormesh: 2.606e-02 (14.77x), pcolorfast: 2.881e-03 (1.63x)
201 evenly-spaced EDGE points: NonUniformImage: 6.852e-04, pcolormesh: 6.031e-03 (8.80x), pcolorfast: 1.412e-03 (2.06x)
101 evenly-spaced EDGE points: NonUniformImage: 5.362e-04, pcolormesh: 4.146e-03 (7.73x), pcolorfast: 1.423e-03 (2.65x)

5001 UNevenly-spaced EDGE points: NonUniformImage: 5.517e-02, pcolormesh: 1.726e+00 (31.29x), pcolorfast: 8.441e-02 (1.53x)
1001 UNevenly-spaced EDGE points: NonUniformImage: 4.643e-03, pcolormesh: 8.060e-02 (17.36x), pcolorfast: 6.322e-03 (1.36x)
501 UNevenly-spaced EDGE points: NonUniformImage: 1.718e-03, pcolormesh: 2.473e-02 (14.40x), pcolorfast: 2.346e-03 (1.37x)
201 UNevenly-spaced EDGE points: NonUniformImage: 7.484e-04, pcolormesh: 6.149e-03 (8.22x), pcolorfast: 1.575e-03 (2.10x)
101 UNevenly-spaced EDGE points: NonUniformImage: 7.105e-04, pcolormesh: 4.676e-03 (6.58x), pcolorfast: 2.282e-03 (3.21x)

@jklymak
Copy link
Member

@jklymak jklymak commented Oct 5, 2020

Have you tried using rasterized=True for pcolormesh?

@eldond
Copy link
Author

@eldond eldond commented Oct 5, 2020

I tested it just now, but it doesn't change speed much and it doesn't put the Z (color) coordinate with the x, y coordinates. pcolorfast does everything I want except it's somewhat less convenient, but I can fix that. I'm going to make a wrapper for pcolorfast that accepts bin centers and makes up reasonable edges to go with them.

@eldond
Copy link
Author

@eldond eldond commented Oct 5, 2020

def pcolorfast_centers(xc, yc, z, ax=None, **kw):
    """
    Estimates bin edges given bin centers and passes them to pcolorfast

    pcolorfast accepts x,y arrays that are one element longer than the corresponding dimensions of z,
    which is awkward when the data are z = f(x, y). Yet pcolorfast is useful enough that we'd like to
    use it, anyway.

    :param xc: 1D float array
        Bin centers in X; length should match first dimension of z

    :param yc: 1D float array
        Bin centers in Y; length should match second dimension of z

    :param z: 2D float array
        Z or color values at the bin centers

    :param ax: Axes instance
        Axes to plot in

    :param kw: additional keywords passed to pcolorfast
    """
    if ax is None:
        ax = plt.gca()
    inner_edges = (xc[:-1] + xc[1:]) / 2.0
    x = np.append(np.append(inner_edges[0] - xc[1] + xc[0], inner_edges), inner_edges[-1] + xc[-1] - xc[-2])
    inner_edges = (yc[:-1] + yc[1:]) / 2.0
    y = np.append(np.append(inner_edges[0] - yc[1] + yc[0], inner_edges), inner_edges[-1] + yc[-1] - yc[-2])
    print(np.shape(x), np.shape(xc), np.shape(z))
    return ax.pcolorfast(x, y, z, **kw)

@anntzer
Copy link
Contributor

@anntzer anntzer commented Oct 8, 2020

Possibly pcolorfast could gain support for shading="nearest"/"flat"/"auto" like pcolor?

@jklymak
Copy link
Member

@jklymak jklymak commented Oct 8, 2020

Yeah, I don't see why not.... Seems a reasonable addition.

@anntzer
Copy link
Contributor

@anntzer anntzer commented Nov 12, 2020

Labeling as good first issue, as there's no API decision to be made here, and figuring out the correct value to display should be straightforward(?).

@Jaroza727
Copy link

@Jaroza727 Jaroza727 commented Dec 4, 2020

Is this issue being worked on? If not, I'd like to work on it

@timhoffm
Copy link
Member

@timhoffm timhoffm commented Dec 8, 2020

Nobody has spoken up to work on this. So go for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

6 participants