Skip to content

Errors in rendering boundaries between filled paths and within a quadmesh #1188

Open
efiring opened this Issue Sep 1, 2012 · 16 comments

5 participants

@efiring
Matplotlib Developers member
efiring commented Sep 1, 2012

This is the underlying problem raised in #1178.
It is illustrated by the test below; note that boundary anomalies are visible in all forms--agg on the screen, and pdf and svg displayed with a viewer--but in different places depending on the viewer and the size of the figure as rendered.
Note that the colorbar is rendered using pcolormesh, which has its own renderer with agg but otherwise is handled by draw_path_collection.

import numpy as np
import matplotlib.pyplot as plt

z = np.arange(150)
z.shape = (10,15)

fig, axs = plt.subplots(2,2)

ax = axs[0,0]
cs0 = ax.contourf(z, 20)
cbar0 = fig.colorbar(cs0, ax=ax)

ax = axs[0,1]
cs1 = ax.contourf(z, 20, alpha=0.3)
cbar1 = fig.colorbar(cs1, ax=ax)

ax = axs[1,0]
im2 = ax.imshow(z, interpolation='nearest')
cbar2 = fig.colorbar(im2, ax=ax)

ax = axs[1,1]
im3 = ax.imshow(z, interpolation='nearest', alpha=0.3)
cbar3 = fig.colorbar(im3, ax=ax)

plt.savefig("test1.pdf")
plt.savefig("test1.svg")
plt.show()
@dmcdougall
Matplotlib Developers member

If you start the code block with ```python you'll get some syntax highlighting.

@jenshnielsen
Matplotlib Developers member

To me it seems like the pdf backend draws them correctly without addition edges however some viewers render these incorrectly and adds some white space. In the agg backend however even without edges there is an overlap between the paths visible only when alpha is less than 1. I guess this is an unrelated bug in the agg drawing code. It is correctly called with no edges. The pngs from the cairo backends are drawn without any overlap. However it does seem like this backend does not support alpha for imshow. I will open a separate issue for this.

@jenshnielsen
Matplotlib Developers member

The pdf problem in evince is probably equivalent to this one
https://bugs.launchpad.net/ubuntu/+source/poppler/+bug/318130
which was reported in 2009

@jenshnielsen
Matplotlib Developers member

Indeed the pdf problem in evince is fixed by applying the patch to poppler linked to in that bug report (0001-Align-fills-to-device-integer-coordinates.patch) so it seems like an aliasing problem.

@jenshnielsen jenshnielsen added a commit to jenshnielsen/matplotlib that referenced this issue Sep 23, 2012
@jenshnielsen jenshnielsen Colorbar Add kw arguement to colorbar to reenable edges around faces in
the colorbar. Resolve problems with white lines in colorbars.
Not enabled by default.  See #1178 and #1188
5f67813
@jenshnielsen jenshnielsen added a commit to jenshnielsen/matplotlib that referenced this issue Sep 26, 2012
@jenshnielsen jenshnielsen Add documentation of colorbar issue #1188 to colorbar documentation 146280b
@dmcdougall
Matplotlib Developers member

Did we decide that this is a viewer problem? If so, should this be closed as it requires an upstream fix?

@jenshnielsen
Matplotlib Developers member

No there is another issue. The agg backend still renders the colorbar with overlap like the pdf did
before meaning that the colorbar with alpha < 1 will look wrong in png. Furthermore alpha is ignored for images (#1193) in the cairo backend so it is not actually possible to render png colorbars with alpha < 1 correctly.

@dmcdougall
Matplotlib Developers member

@jenshnielsen Thanks for clarifying.

@awehrfritz

Is there still somebody looking into this one? I'm using matplotlib 1.4.2 (linux-x64) and still get these white stripes in the colorbar or contourf plots.

I tested the pdf-files with various readers, i.e. okular (libpoppler), google-chrome, Adobe Reader/Acrobat XI‎ (both Windows), and always see these stripes.
Interestingly, for the ps-files the issue is less pronounced but I would say still present.
The svg-files also have the same issue as the pdf-files.

The stripes almost disappear when I plot the same data twice (see the script below), but I consider this as a very bad workaround.

The only fix that works consistently is to set edgecolor="face", but I guess that was also regarded as a workaround and not a fix.

Here is a script that shows how to reproduce and work around the white stripes for contourf plots and the colorbar. I hope it helps to debug and fix this problem.

#!/usr/bin/env python

""" This script illustrates a rendering bug for PDF and SVG files
"""

from matplotlib import pyplot as plt
import numpy as np

plt.close('all')

def gaussian_2d(x, y, x0, y0, xsig, ysig):
    return np.exp(-0.5*(((x-x0) / xsig)**2 + ((y-y0) / ysig)**2))

delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z = gaussian_2d(X, Y, 1., 1., 1.5, 0.5)

fig, axs = plt.subplots(2, 2, sharex=True, sharey=True)

ax = axs[0,0]
C = ax.contourf(X,Y,Z, 50, cmap='gnuplot')
fig.colorbar(C, ax=ax)
ax.set_title('contourf')

ax = axs[0,1]
C = ax.contourf(X,Y,Z, 50, cmap='gnuplot')
C = ax.contourf(X,Y,Z, 50, cmap='gnuplot')
fig.colorbar(C, ax=ax)
ax.set_title('2x contourf')

ax = axs[1,0]
C = ax.contourf(X,Y,Z, 50, cmap='gnuplot')
Cbar = fig.colorbar(C, ax=ax)
Cbar.solids.set_edgecolor("face")
plt.setp(C.collections, edgecolor="face")
ax.set_title('contourf + edgecolor="face"')

ax = axs[1,1]
C = ax.contourf(X,Y,Z, 50, cmap='gnuplot')
plt.setp(C.collections, rasterized=True)
fig.colorbar(C, ax=ax)
ax.set_title('contourf + rasterized')

fig.savefig('./Gaussian2D.ps')
fig.savefig('./Gaussian2D.pdf')
fig.savefig('./Gaussian2D.svg')
fig.show()
@jenshnielsen
Matplotlib Developers member

As far as I understand this is a bug in the pdf renders which there is very little we can do about. If you draw 2 faces next to each other with no overlap a gab will often show due roundoff/snapping (Adobe seems to handle this better than most others in my tests).

We can't draw them with an overlap in general since that would break partially transparent contours with alpha< 1. Try adding alpha=0.5 to all your contourf calls to see what I mean i.e ax.contourf(X,Y,Z, 50, cmap='gnuplot', alpha=0.5) and so on.

Using edgecolor='face' is a workaround which adds a small border to all faces. The border gives an non zero overlap which looks strange when alpha<1 but is otherwise perfectly fine. I would consider that a way better solution than plotting the data twice.

@awehrfritz

Thanks for your quick answer!

Alright, I see! So it really is related to the PDF renderer after all, this hasn't been clear to me from the discussion above.
But, as I said all PDF renderer I tested, including the newest Adobe ones, show the same issue. I cannot even say that Adobe is doing better then the other ones.

So the reason why edgecolor='face' is not set by default is due to issues related to transparency. In that case, couldn't one add edgecolor='face' for all plots where alpha is not explicitly set, something like:

if alpha is None:
    edgecolor='face'

I have no idea of the matplotlib internals, so I don't know where this should be best implemented.

@jenshnielsen
Matplotlib Developers member

It might be possible to do something like that. I would have to think some more about this.

In my original use case way back in 2012 this was only an issue for colorbars where the edges are straight. In this case Adobe seems to handle it correctly and the patch in the attached bug report fixed it for Evince on Ubuntu. It seems to be somewhat worse in your use case and still not really fixed.

Briefly:

Using preview on OSX:
I see stripes in both the contourf and the colorbar.

Using Adobe reader XI:
I only see the stripes in the contourf but the colorbar looks fine.

@awehrfritz

I actually agree with you that for plots with alpha=1 this workaround just works "perfectly fine".

So, I took a look at the source code, and for contourf plots, this workaround could probably be implemented like this:

--- anaconda/lib/python2.7/site-packages/matplotlib/contour.py  2014-10-23 20:53:19.000000000 +0300
+++ tmp/contour.py  2015-02-03 16:45:46.267598199 +0200
@@ -942,10 +942,14 @@
                 paths = self._make_paths(segs, kinds)
                 # Default zorder taken from Collection
                 zorder = kwargs.get('zorder', 1)
+                if self.alpha is None:
+                    edgecolors='face'
+                else
+                    edgecolors='none'
                 col = mcoll.PathCollection(
                     paths,
                     antialiaseds=(self.antialiased,),
-                    edgecolors='none',
+                    edgecolors=edgecolors,
                     alpha=self.alpha,
                     transform=self.get_transform(),
                     zorder=zorder)
@awehrfritz

For pcolormesh this could be fixed directly in the high level function declaration like this:

--- anaconda/lib/python2.7/site-packages/matplotlib/axes/_axes.py   2015-02-03 16:35:25.849881579 +0200
+++ tmp/_axes.py    2015-02-03 18:13:43.624257733 +0200
@@ -5083,7 +5083,11 @@
         vmax = kwargs.pop('vmax', None)
         shading = kwargs.pop('shading', 'flat').lower()
         antialiased = kwargs.pop('antialiased', False)
-        kwargs.setdefault('edgecolors', 'None')
+        if alpha is None:
+            kwargs.setdefault('edgecolors', 'face')
+            kwargs.setdefault('linewidths', 0.0)
+        else
+            kwargs.setdefault('edgecolors', 'None')

         allmatch = (shading == 'gouraud')

Please note that all these suggestions are untested, and the colorbar would still need some additional work, since the function call to pcolormesh sets edgecolors='None' explicitly.

@tacaswell
Matplotlib Developers member

@dkxls Sorry your comments went unnoticed for so long. Can you put those changes in a PR?

@efiring
Matplotlib Developers member
efiring commented Jul 22, 2015

I'm uncomfortable with this hack; at the very least it needs to be checked with all backends to see what it actually does. Although the postscript standard is for linewidths of zero to be a device-dependent minimum, I think that in all backends we are treating linewidths of zero as "don't draw that line at all". If so, the hack will need some arbitrary small value for the linewidths.

@efiring
Matplotlib Developers member
efiring commented Jul 22, 2015

As far as the colorbar is concerned, the problem has been reduced by #4481, which is also a bit of a hack.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.