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

Remove spacing between polygons #2823

Closed
letmaik opened this issue Feb 20, 2014 · 22 comments
Closed

Remove spacing between polygons #2823

letmaik opened this issue Feb 20, 2014 · 22 comments

Comments

@letmaik
Copy link
Contributor

letmaik commented Feb 20, 2014

test image

I believe the spacing between the polygons shouldn't be there. Below is my code. Is this a bug or am I missing some option? It happens on all backends.

I know that I can enlarge the polygons slightly so that they overlap each other which then removes these spaces, but this approach is only suitable for very simple polygons like the rectangular ones above, and in my real use-case I have arbitrarily shaped polygons, so that's not really an option.

import numpy as np

import matplotlib as mpl
mpl.use('agg')
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection

def createPolygons(xv, yv):
    assert xv.shape == yv.shape
    xy = np.dstack((xv,yv))

    # adapted from matplotlib.collections.QuadMesh.convert_mesh_to_paths
    verts = np.concatenate((
                xy[0:-1, 0:-1],
                xy[0:-1, 1:  ],
                xy[1:  , 1:  ],
                xy[1:  , 0:-1],
                ), axis=2)
    verts = verts.reshape((xv.shape[0]-1) * (xv.shape[1]-1), 4, 2)

    return verts

def drawPlot(verts, outputFile, blackbg, widthPx):
    if blackbg:
        mpl.rcParams['savefig.facecolor'] = 'black'
    else:
        mpl.rcParams['savefig.facecolor'] = 'white'

    ax = plt.gca()
    fig = plt.gcf()

    if blackbg:
        fig.patch.set_facecolor('black')
        ax.patch.set_facecolor('black')
        ax.spines['bottom'].set_color('white')
        ax.spines['top'].set_color('white')
        ax.spines['left'].set_color('white')
        ax.spines['right'].set_color('white')
        for i in ax.get_xticklabels(): i.set_color("white")
        for i in ax.get_yticklabels(): i.set_color("white")
        for t in ax.xaxis.get_ticklines(): t.set_color('white')
        for t in ax.yaxis.get_ticklines(): t.set_color('white')
        ax.xaxis.label.set_color('white')
        ax.yaxis.label.set_color('white')

    coll = PolyCollection(verts, facecolors=None, edgecolors='none')

    ax.add_collection(coll)
    ax.autoscale()

    width = fig.get_figwidth()
    dpi = widthPx / width

    fig.savefig(outputFile, dpi=dpi)
    plt.close(fig)


x = np.linspace(0, 10, num=10)
xv, yv = np.meshgrid(x, x)
verts = createPolygons(xv, yv)
drawPlot(verts, 'test.png', blackbg=True, widthPx=500)
@WeatherGod
Copy link
Member

In your code, you set edgecolor to be 'none'. That means that the edge
won't be drawn at all. However, I would have thought that the faces would
still touch each other. Note, that what you are doing is very similar to
what pcolor() (or maybe pcolormesh()?) does. Are there differences if you
increase/decrease the dpi? I also wonder if there are differences between
snap=True and snap=False for the PolyCollection.

Lastly, which version of matplotlib are you using?

Cheers!
Ben Root

On Thu, Feb 20, 2014 at 4:03 AM, Maik Riechert notifications@github.comwrote:

[image: test image]https://f.cloud.github.com/assets/530988/2216573/1a9f9448-9a0d-11e3-8288-d4c37dbefc92.png

I believe the spacing between the polygons shouldn't be there. Below is my
code. Is this a bug or am I missing some option? It happens on all
backends.

I know that I can enlarge the polygons slightly so that they overlap each
other which then removes these spaces, but this approach is only suitable
for very simple polygons like the rectangular ones above, and in my real
use-case I have arbitrarily shaped polygons, so that's not really an option.

import numpy as np
import matplotlib as mplmpl.use('agg')import matplotlib.pyplot as pltfrom matplotlib.collections import PolyCollection
def createPolygons(xv, yv):
assert xv.shape == yv.shape
xy = np.dstack((xv,yv))

# adapted from matplotlib.collections.QuadMesh.convert_mesh_to_paths
verts = np.concatenate((
            xy[0:-1, 0:-1],
            xy[0:-1, 1:  ],
            xy[1:  , 1:  ],
            xy[1:  , 0:-1],
            ), axis=2)
verts = verts.reshape((xv.shape[0]-1) * (xv.shape[1]-1), 4, 2)

return verts![test](https://f.cloud.github.com/assets/530988/2216573/1a9f9448-9a0d-11e3-8288-d4c37dbefc92.png)

def drawPlot(verts, outputFile, blackbg, widthPx):
if blackbg:
mpl.rcParams['savefig.facecolor'] = 'black'
else:
mpl.rcParams['savefig.facecolor'] = 'white'

ax = plt.gca()
fig = plt.gcf()

if blackbg:
    fig.patch.set_facecolor('black')
    ax.patch.set_facecolor('black')
    ax.spines['bottom'].set_color('white')
    ax.spines['top'].set_color('white')
    ax.spines['left'].set_color('white')
    ax.spines['right'].set_color('white')
    for i in ax.get_xticklabels(): i.set_color("white")
    for i in ax.get_yticklabels(): i.set_color("white")
    for t in ax.xaxis.get_ticklines(): t.set_color('white')
    for t in ax.yaxis.get_ticklines(): t.set_color('white')
    ax.xaxis.label.set_color('white')
    ax.yaxis.label.set_color('white')


coll = PolyCollection(verts, facecolors=None, edgecolors='none')![test](https://f.cloud.github.com/assets/530988/2216551/d950516c-9a0c-11e3-9863-8053e8b1529f.png)![test](https://f.cloud.github.com/assets/530988/2216561/eb17d776-9a0c-11e3-89b1-5d92d8099537.png)


ax.add_collection(coll)
ax.autoscale()

width = fig.get_figwidth()
dpi = widthPx / width

fig.savefig(outputFile, dpi=dpi)
plt.close(fig)

x = np.linspace(0, 10, num=10)xv, yv = np.meshgrid(x, x)verts = createPolygons(xv, yv)drawPlot(verts, 'test.png', blackbg=True, widthPx=500)

Reply to this email directly or view it on GitHubhttps://github.com//issues/2823
.

@letmaik
Copy link
Contributor Author

letmaik commented Feb 20, 2014

Changing the dpi seems to keep the edge size always at 1px with Agg.
When I decrease the polygon size (by increasing 'num' to 500) then the
edges are not visible anymore but the blue color gets darker, probably
because it mixes the edge spaces (=black) with blue when rendering.
There are no differences between snap=True and False.

I use matplotlib 1.1.1rc, the latest version in the Ubuntu 12.04 repos.

Also, my real data is RGB colors and the coordinates are not in any
regular form, so I really need to break it down into plain polygons
where each has its own facecolor. I just stripped all that out here as
it doesn't add to the actual problem.

Cheers
Maik

On 20/02/14 15:45, Benjamin Root wrote:

In your code, you set edgecolor to be 'none'. That means that the edge
won't be drawn at all. However, I would have thought that the faces would
still touch each other. Note, that what you are doing is very similar to
what pcolor() (or maybe pcolormesh()?) does. Are there differences if you
increase/decrease the dpi? I also wonder if there are differences between
snap=True and snap=False for the PolyCollection.

Lastly, which version of matplotlib are you using?

Cheers!
Ben Root

On Thu, Feb 20, 2014 at 4:03 AM, Maik Riechert
notifications@github.comwrote:

[image: test
image]https://f.cloud.github.com/assets/530988/2216573/1a9f9448-9a0d-11e3-8288-d4c37dbefc92.png

I believe the spacing between the polygons shouldn't be there. Below
is my
code. Is this a bug or am I missing some option? It happens on all
backends.

I know that I can enlarge the polygons slightly so that they overlap
each
other which then removes these spaces, but this approach is only
suitable
for very simple polygons like the rectangular ones above, and in my
real
use-case I have arbitrarily shaped polygons, so that's not really an
option.

import numpy as np
import matplotlib as mplmpl.use('agg')import matplotlib.pyplot as
pltfrom matplotlib.collections import PolyCollection
def createPolygons(xv, yv):
assert xv.shape == yv.shape
xy = np.dstack((xv,yv))

adapted from matplotlib.collections.QuadMesh.convert_mesh_to_paths

verts = np.concatenate((
xy[0:-1, 0:-1],
xy[0:-1, 1: ],
xy[1: , 1: ],
xy[1: , 0:-1],
), axis=2)
verts = verts.reshape((xv.shape[0]-1) * (xv.shape[1]-1), 4, 2)

return
vertstest
def drawPlot(verts, outputFile, blackbg, widthPx):
if blackbg:
mpl.rcParams['savefig.facecolor'] = 'black'
else:
mpl.rcParams['savefig.facecolor'] = 'white'

ax = plt.gca()
fig = plt.gcf()

if blackbg:
fig.patch.set_facecolor('black')
ax.patch.set_facecolor('black')
ax.spines['bottom'].set_color('white')
ax.spines['top'].set_color('white')
ax.spines['left'].set_color('white')
ax.spines['right'].set_color('white')
for i in ax.get_xticklabels(): i.set_color("white")
for i in ax.get_yticklabels(): i.set_color("white")
for t in ax.xaxis.get_ticklines(): t.set_color('white')
for t in ax.yaxis.get_ticklines(): t.set_color('white')
ax.xaxis.label.set_color('white')
ax.yaxis.label.set_color('white')

coll = PolyCollection(verts, facecolors=None,
edgecolors='none')testtest

ax.add_collection(coll)
ax.autoscale()

width = fig.get_figwidth()
dpi = widthPx / width

fig.savefig(outputFile, dpi=dpi)
plt.close(fig)

x = np.linspace(0, 10, num=10)xv, yv = np.meshgrid(x, x)verts =
createPolygons(xv, yv)drawPlot(verts, 'test.png', blackbg=True,
widthPx=500)

Reply to this email directly or view it on
GitHubhttps://github.com//issues/2823
.


Reply to this email directly or view it on GitHub
#2823 (comment).

@letmaik
Copy link
Contributor Author

letmaik commented Mar 1, 2014

I just tested with 1.3.1 and the problem doesn't appear anymore with agg and pdf. With svg I can still see a slight spacing (although it seems smaller than before). So somewhere between 1.1.1rc and 1.3.1 there must have been a fix. It's a shame that the Ubuntu repos lag behind so much.

@letmaik
Copy link
Contributor Author

letmaik commented Mar 1, 2014

It seems I cheered too early. When using basemap, the issue persists:

test_stereo

With overlapping workaround:
test_stereo_overlap

I'm not sure yet if this is really related to basemap or rather the non-rectangular polygon shape (due to map projection).

Wouldn't life be a lot easier if matplotlib would accept polygoncollections defined by vertices and indices? Then it is absolutely clear for the renderer which polygon edges are identical in case two polygons share an edge.

@tacaswell
Copy link
Member

@neothemachine Did you pin this down with basemap?

@letmaik
Copy link
Contributor Author

letmaik commented Apr 20, 2014

It is independent of basemap. See below just using a normal matplotlib plot:

test_nooverlap

With overlapping workaround:
test_overlap

@tacaswell
Copy link
Member

Punting this to 1.5 as it sounds like a) you have a usable work-around and b) fixing it is probably non-trivial.

@tacaswell tacaswell added this to the v1.5.x milestone Apr 20, 2014
@efiring
Copy link
Member

efiring commented Apr 21, 2014

This sounds like a perennial problem for which no one has found a solution, because the problem is in the renderers; is there any point in leaving this as an open issue?

@letmaik
Copy link
Contributor Author

letmaik commented Apr 21, 2014

@tacaswell Regarding the work-around, it comes with a severe loss in accuracy as I just make the polygons bigger (at the bottom and right), you can see it clearly at the bottom of the plots.

You can close it if you want, I just don't want it to get lost as I think this issue is quite important.

@tacaswell
Copy link
Member

I think this should be left open (or at a minimum replaced) as a feature request for a way to pass in an arbitrary mesh + colors. That is why I punted to 1.5.

For you expanding workaround, why not push all of the edges out equally? Find the center of mass of the polygon and then move each point out by some delta along the line from the center to the point

point = point + delta * (point - com) / | point - com |

which should help reduce the distortion.

@letmaik
Copy link
Contributor Author

letmaik commented Apr 21, 2014

For you expanding workaround, why not push all of the edges out
equally? Find the center of mass of the polygon and then move each
point out by some delta along the line from the center to the point

Well, yes, but I couldn't find a fast way to do that using numpy. I
have half a million polygons at times and don't want to spend more than
roughly half a second running such workaround. I don't need publication
quality at the moment, so it's fine for me in the meantime.

@efiring
Copy link
Member

efiring commented Apr 21, 2014

Trying to solve this by pushing the edges out, so there is some overlap, will cause new artifacts if alpha is not 1.

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

pelson commented Dec 20, 2018

I just tried the attached code on master and did not see the issue that was reported. I therefore think this is now resolved.

@tacaswell wrote:

I think this should be left open (or at a minimum replaced) as a feature request for a way to pass in an arbitrary mesh + colors. That is why I punted to 1.5.

This hasn't been done AFAIK, but should definitely be a separate issue if it does actually need to be tracked (I'm sceptical).

Finally, I think the original question was perhaps a little contrived? If one really wanted to use a bunch of polygons to represent an image you might consider using a pcolormesh. An example of a curved pcolormesh is here (I'm showing it without data to highlight that there are no unsightly borders):

import matplotlib.pyplot as plt
import numpy as np


θ, r = np.meshgrid(np.linspace(0, np.pi, 15), np.linspace(1, 5, 10))
verts = np.stack([np.sin(θ)*r, np.cos(θ)*r], axis=-1)
cmap = plt.get_cmap('Reds')

c = cmap(verts[:, :, 0].flatten()/verts[:, :, 0].max())
plt.pcolormesh(verts[:, :, 0], verts[:, :, 1],
               np.ones(verts[:-1, :-1, 0].shape))

plt.show()

figure_1

Yet another alternative is to use a library that does the image transformation for you - cartopy has an example of doing this at https://scitools.org.uk/cartopy/docs/latest/gallery/geostationary.html#sphx-glr-gallery-geostationary-py.

Whilst I have the power to close the issue, I'm resisting given the suggestion from @tacaswell. I'll leave it to him to make the final call.

@anntzer
Copy link
Contributor

anntzer commented Jan 4, 2019

PolyCollections are basically bound to always have this problem; drawing a quadmesh is indeed the way to go.

On the other hand, perhaps this suggests that what's needed is a way for PolyCollections to convert themselves to QuadMeshes (triangulating ("quadrangulating"?) larger polygons on-demand -- that's the "interesting" part) for drawing?
But this requires drawing unstructured meshes (i.e. with quads at aribtrary positions), whereas quadmeshes are, well, structured (always contiguous quads), although that's a limitation on Matplotlib's side, not on the renderer. Hmm...

@QuLogic QuLogic self-assigned this Jul 21, 2020
@raphaelquast
Copy link
Contributor

any updates on this?
I'm experiencing similar problems when plotting a collection of ~100K polygons.
Using coll.set_edgecolors(coll.get_facecolors()) and a linewidth according to the zoom seems to solve the problem on my side but I'd very much prefer a general solution that does not need manual tweaking of linewidths...

@WeatherGod
Copy link
Member

WeatherGod commented Oct 6, 2021 via email

@raphaelquast
Copy link
Contributor

hey @WeatherGod, thanks for the suggestion!
I gave it a quick try, and it seems to work nice and really fast.
Unfortunately the spacing of the PolygonCollection is still present...

For now, I found that converting the PolygonCollection into a TriMesh (e.g. by simply dividing each rectangle in the diagonal)
can be used to create the desired (edge-free) appearance...

@jklymak
Copy link
Member

jklymak commented Oct 7, 2021

As @anntzer points out above, this is extremely hard to get rid of in general, and is a pretty big problem in graphics: if you are going to antialias a polygon, you need to know what its background is. Matplotlib's backends have no sophisticated way to know this, so they antialias to the background color of the axes.

Its fun to think about fixing this somehow, but it would have to be pretty low level in the rendering, so I think you would need to write your own backend.

@raphaelquast
Copy link
Contributor

@jklymak
yes I can imagine that this is a quite subtle problem... writing a backend however is far beyond my level of expertise... 😄

The only thing I don't really understand is how TriMesh is then capable of circumventing this rendering problem ?
(is this connected to the irremovable edges that appear if transparency is used?)

To illustrate what I mean I've create a minimal example (code is in "Details"):

The left rows are plotted using a PolyCollection while the right rows use a TriMesh

Figure_27

import numpy as np
from matplotlib.collections import PolyCollection
from matplotlib.tri import TriMesh, Triangulation
import matplotlib.pyplot as plt

def tri_rectangles(verts, z=None):
    # convert rectangles to 2 triangles by splitting it in the diagonal
    x = np.vstack([verts[:, 2][:, 0], 
                   verts[:, 3][:, 0], 
                   verts[:, 1][:, 0]]).T.ravel()
    y = np.vstack([verts[:, 2][:, 1], 
                   verts[:, 3][:, 1], 
                   verts[:, 1][:, 1]]).T.ravel()
    x2 = np.vstack([verts[:, 3][:, 0], 
                    verts[:, 0][:, 0], 
                    verts[:, 1][:, 0]]).T.ravel()
    y2 = np.vstack([verts[:, 3][:, 1], 
                    verts[:, 0][:, 1], 
                    verts[:, 1][:, 1]]).T.ravel()
    
    x = np.append(x, x2)
    y = np.append(y, y2)
    
    tri = Triangulation(
        x, y, triangles=np.array(range(len(x))).reshape((len(x) // 3, 3))
    )
    
    if z is not None:
        z = np.tile(np.repeat(z, 3), 2)
        
    return tri, z

verts = np.array([[[ 9.63981913, 44.62741306],
                   [ 9.6274869 , 44.62568775],
                   [ 9.62980296, 44.61689049],
                   [ 9.64213333, 44.61861546]],
           
                  [[ 9.65215211, 44.62913692],
                   [ 9.63981913, 44.62741306],
                   [ 9.64213333, 44.61861546],
                   [ 9.65446446, 44.62033897]],
           
                  [[ 9.66448586, 44.63085932],
                   [ 9.65215211, 44.62913692],
                   [ 9.65446446, 44.62033897],
                   [ 9.66679635, 44.62206104]],
                  ])

f, ax = plt.subplots(figsize=(12,5))
# --------------------------------- plot with a PolyCollection
coll = PolyCollection(verts)
coll.set_array(np.array([1,1,1]))
ax.add_collection(coll)

coll = PolyCollection(verts + .0125)
coll.set_array(np.array([1,1,1]))
coll.set_alpha(.5)
ax.add_collection(coll)

coll = PolyCollection(verts + .025)
coll.set_array(np.array([1,2,3]))
ax.add_collection(coll)
                  
# --------------------------------- plot with a TriMesh
verts[:,:,0] += 0.045

tri, z_tri = tri_rectangles(verts, [1,1,1])
tri_coll = TriMesh(tri)
tri_coll.set_array(z_tri)
ax.add_collection(tri_coll)

tri, z_tri = tri_rectangles(verts + .0125, [1,1,1])
tri_coll = TriMesh(tri)
tri_coll.set_array(z_tri)
tri_coll.set_alpha(.5)
ax.add_collection(tri_coll)

tri, z_tri = tri_rectangles(verts + .025, [1,2,3])
tri_coll = TriMesh(tri)
tri_coll.set_array(z_tri)
ax.add_collection(tri_coll)

ax.set_xlim(9.623228860352405, 9.740110942312576)
ax.set_ylim(44.615001488088424, 44.6577688106626)

f.tight_layout()

@jklymak
Copy link
Member

jklymak commented Oct 7, 2021

PolyCollection is just a bunch of polygons, usually not even adjacent to each other, hence they have no concept of neighbours. Quadmesh and Trimesh know they are part of one large object and hence the cells know about their neighbours and can do a better job of antialiasing.

@github-actions
Copy link

github-actions bot commented Mar 4, 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 Mar 4, 2023
@QuLogic
Copy link
Member

QuLogic commented Mar 4, 2023

Closing as a duplicate of #1188

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

No branches or pull requests

10 participants