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

inaccurate marker positioning in plot() #7233

Open
anntzer opened this issue Oct 7, 2016 · 19 comments
Open

inaccurate marker positioning in plot() #7233

anntzer opened this issue Oct 7, 2016 · 19 comments
Labels
Difficulty: Hard https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues keep Items to be ignored by the “Stale” Github Action

Comments

@anntzer
Copy link
Contributor

anntzer commented Oct 7, 2016

mpl 1.5.3/2.0b4/master, no matplotlibrc

_xs = np.linspace(0, 30, 201); plt.plot(_xs, scipy.stats.lognorm.pdf(_xs / 14, .472) / 14, ".")

plot

Note the slight inaccuracy in positioning of the markers at the positioning of the markers at the positions marked with the red circles, resulting in a "step-like" behavior (you may need to open the image in a larger viewer to notice it).

The data itself does not present this behavior, as can be checked just by zooming in these regions.

@efiring
Copy link
Member

efiring commented Oct 7, 2016

I wonder whether it is just a limitation on the ability of antialiasing to deliver a visual impression of sub-pixel accuracy. The steppiness is at the one-pixel level, but it doesn't look like simple pixel-snapping.

@tacaswell
Copy link
Member

Turning snapping off does not help that much. I wonder if this is related to the way we 'stamp' the markers?

attn @mdboom

@tacaswell tacaswell added this to the 2.0.1 (next bug fix release) milestone Oct 8, 2016
@tacaswell
Copy link
Member

import numpy
import scipy.stats
import matplotlib.pyplot as plt
plt.ion()
fig, ax = plt.subplots()
_xs = np.linspace(0, 30, 201); 
ln, = ax.plot(_xs, scipy.stats.lognorm.pdf(_xs / 14, .472) / 14, ".")

@tacaswell tacaswell modified the milestones: 2.0 (style change major release), 2.0.1 (next bug fix release) Oct 29, 2016
@NelleV NelleV modified the milestones: 2.1 (next point release), 2.0 (style change major release) Nov 10, 2016
@mdboom
Copy link
Member

mdboom commented Dec 5, 2016

Yes, it's definitely related to how we stamp the markers. We don't do individual subpixel positioning for the markers. We could do that but it would be significantly slower (so should be a user-configurable option).

@gson1703
Copy link

gson1703 commented Aug 3, 2017

I'm also suffering from this issue. See the attached script and image for an example of what happens to a grid of evenly spaced markers. This level of quality is simply not acceptable for professional use - it looks like something out of the 1980s, but this 2017, and subpixel positioning is no longer optional.

grid.py.txt
detail

@WeatherGod
Copy link
Member

Could you repost that example. The link appears to be broken.

@WeatherGod
Copy link
Member

I would be curious if this is something that could be resolved with the Cairo rework?

@gson1703
Copy link

gson1703 commented Aug 3, 2017

import math
import numpy
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
plt.style.use('dark_background')
fig = plt.figure()
ax = fig.add_subplot(111)
xv = []
yv = []
for y in range(10):
    for x in range(10):
        xv.append(x * 0.147)
        yv.append(y * 0.147)
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
plt.scatter(xv, yv)
fig.savefig('grid.png')

@anntzer
Copy link
Contributor Author

anntzer commented Aug 3, 2017

@WeatherGod It is indeed solved by mpl_cairo (in fact this was one of the original motivations for writing it). See second example at https://gitter.im/matplotlib/matplotlib?at=5963202789aea4761d81a829. (You even commenter right under it :-))
As a side note mpl_cairo also stamps the markers much faster (there's a timing script in the examples).

@WeatherGod
Copy link
Member

WeatherGod commented Aug 3, 2017 via email

@anntzer
Copy link
Contributor Author

anntzer commented Aug 3, 2017

I'm sure you can technically make it work in Agg... if someone actually understands the Agg rendering code paths (I gave up due to complete lack of documentation on Agg's side). The relevant part, which is at https://github.com/anntzer/mpl_cairo/blob/master/src/_mpl_cairo.cpp#L540 (cache preparation) and https://github.com/anntzer/mpl_cairo/blob/master/src/_mpl_cairo.cpp#L588 (marker stamping), should be fairly self explanatory if you know C++ -- I make a cache of renderered markers with various subpixel offsets (decided according to the path.simplify_theshold, because why not) and then the stamping loop just needs to pull out the right one each time.

There's some discussion about using a recent g++ on a manylinux1 docker, which also involves statically linking a recent libstdc++ into a manylinux1 wheel, so that's apparently doable too... but tbh it's not very high on my priority list now.

@tacaswell tacaswell modified the milestones: 2.1 (next point release), 2.2 (next next feature release) Oct 3, 2017
@ImportanceOfBeingErnest
Copy link
Member

There is a nice visualization of this issue brought up in this Stackoverflow question.

Running this with TkCairo shows the same issue as with TkAgg in the saved animation.
Interestingly the scatter animates smoothly on screen with Cairo backend, while there is no difference between the animation on screen and the saved gif for Agg backend. (@anntzer Is this difference to be expected?)

skewedgridtkcairo

Code for reproduction:

import numpy as np
import matplotlib
matplotlib.use("TkCairo")
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.animation import FuncAnimation

x, y = np.meshgrid(np.linspace(-1,1,31),np.linspace(-1,1,31))

fig, (ax1,ax2) = plt.subplots(ncols=2, figsize=(5,2.8))
fig.subplots_adjust(.02,.02,.98,.9,.03,.03)
fig.suptitle(matplotlib.get_backend())
ax1.set_title("scatter")
ax2.set_title("PatchCollection")
### Scatter
sc = ax1.scatter(x,y,s=2)
### PatchCollection
patches=[]
for i in range(x.shape[0]):
    for j in range(x.shape[1]):
        patches.append(plt.Circle((x[i,j],y[i,j]),0.015))
        
class UpdatablePatchCollection(PatchCollection):
    def __init__(self, patches, *args, **kwargs):
        self.patches = patches
        PatchCollection.__init__(self, patches, *args, **kwargs)
    def get_paths(self):
        self.set_paths(self.patches)
        return self._paths
    
pc = UpdatablePatchCollection(patches)
ax2.add_collection(pc)


for ax in (ax1,ax2):
    ax.axis("off")
    ax.set_xlim(-1.2,1.2)
    ax.set_ylim(-1.2,1.2)
    ax.set_aspect('equal')

def generator():
    X=np.copy(x); Y=np.copy(y)
    for i in range(100):
        X += 0.0015*np.cos(x+3*y)
        Y += 0.0015*np.sin(6*x-4*y)
        yield (X,Y)

def animate(i): 
    x,y = i
    # update scatter
    sc.set_offsets(np.c_[x.flatten(),y.flatten()])
    # update PatchCollection
    for circle, xi,yi in zip(patches, x.flatten(),y.flatten()):
        circle.center = (xi,yi)
    pc.pchanged()

ani = FuncAnimation(fig, animate, frames=generator, interval=50, repeat=False)
ani.save("skewedgrid{}.gif".format(matplotlib.get_backend()), writer="imagemagick")
plt.show()

@anntzer
Copy link
Contributor Author

anntzer commented Mar 18, 2018

The difference is due to the fact that when saving an animation, we internally fallback on agg, because backend_cairo did not implement a way to dump the raw buffer. #10828 fixes that issue.

You may also be interested in https://github.com/anntzer/mplcairo for a faster version...

@gokceneraslan
Copy link

gokceneraslan commented Dec 25, 2018

Just to make sure, these weird horizontal and vertical patterns below are due to this issue basically, right?

import numpy as np
import matplotlib.pyplot as plt

x = np.tile(np.arange(100), 100)
y = np.repeat(np.arange(100), 100)

f, ax = plt.subplots(figsize=(7, 7))
ax.scatter(x, y, s=1, c='k')

image

@anntzer
Copy link
Contributor Author

anntzer commented Dec 25, 2018

Yes.

@jklymak
Copy link
Member

jklymak commented Feb 12, 2019

Is there really an action item for this? If not, I'll leave open but un-milestone.

@jklymak jklymak modified the milestones: v3.1.0, unassigned Feb 12, 2019
@tacaswell
Copy link
Member

Yes, instead of generating 1 stamp and snapping to the nearest pixel, generate N and snap to nearest fraction of a pixel.

@tacaswell tacaswell modified the milestones: unassigned, v3.2.0 Feb 12, 2019
@anntzer anntzer modified the milestones: v3.2.0, v3.3.0 Aug 13, 2019
@anntzer anntzer added the Difficulty: Hard https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues label Aug 13, 2019
@Jona-L
Copy link

Jona-L commented Apr 22, 2020

not sure if related or said before, but either:

  • the pixel marker position is 1 pixel too high when other markers are fine
  • other markers positions are 1 pixel too low, while the pixel marker position if fine
    (depending on if the axis is positioned correctly as well)

example:

plt.xlim(0, 3)
plt.ylim(0, 3)
plt.plot(.05, 1, ',b')
plt.plot(.1, 1, '.b')
plt.plot(.2, 1, 'ob', ms=10, mew=1)
plt.plot(.3, 1, ',b')
plt.plot(.3, 1, 'or', fillstyle='none', ms=10, mew=1)
plt.plot(.4, 1, '.b', ms=9)
plt.plot(.4, 1, 'or', fillstyle='none', ms=10, mew=1)    
plt.savefig('tst.png', dpi=100)

image

@github-actions
Copy link

github-actions bot commented Oct 9, 2023

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 Oct 9, 2023
@anntzer anntzer added keep Items to be ignored by the “Stale” Github Action and removed status: inactive Marked by the “Stale” Github Action labels Oct 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Difficulty: Hard https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues keep Items to be ignored by the “Stale” Github Action
Projects
None yet
Development

No branches or pull requests