Images shifted relative to other plot feature in vector graphic output formats #1085

Closed
technobauble opened this Issue Aug 14, 2012 · 9 comments

6 participants

@technobauble

When writing out plots in vector graphics formats, the image data can be shifted relative to the axes window.

Here is a list of things I've noticed about the problem:

  • When data is shifted outside the axes window, it is clipped. Sometimes the data will be shifted entirely outside the axes window and the plot will appear blank.
  • I've observed the problem in ps/eps/pdf/svg output files.
  • The interpolation method used seems to have an effect on the shifted result.
  • All of the image data is contained within the output file and can be retrieved using a program like inkscape (although some digging is required)
  • The shift seems to be different in different viewers. For example, pdf output looks okay in Okular but not in xpdf.

There have been a couple of emails about this on the users mailing list. Benjamin Root verified the problem. Eric Firing suggested that it might be a truncation error bug and suggested the following:

I thought it might come from the use of str() instead of repr() when
generating the concat matrix in the ps backend, but that's not it. I'm
suspecting it may be inherent in the ps language, when one uses a matrix
to make an enormous translation in one direction, and then uses
translate to sling everything back in the other direction. Either that,
or some sort of "snapping" is going on.

--Chad

Here are some scripts that can be used to reproduce the problem:

import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm

imgData = [[1.0/(x) + 1.0/(y) for x in range(1,100)] for y in range(1,100)]

tMin=734717.945208
tMax=734717.946366

# Case 0: Works
Case0={"id":0,
       "interp":None,
       "extents":(tMin,tMax,1,100)}

# Case 1: Works
Case1={"id":1,
       "interp":"none",
       "extents":(1,100,1,100)}

# Case 2: Doesn't work
Case2={"id":2,
       "interp":"none",
       "extents":(tMin,tMax,1,100)}

for Case in (Case2, Case1, Case0):
    plt.figure(Case["id"])
    axImg=plt.subplot(111)
    axImg.imshow(imgData,
                    norm=LogNorm(),
                    interpolation=Case["interp"],
                    extent=Case["extents"])
    axImg.set_aspect('auto')

    for filetype in ["png", "eps", "pdf", "svg"]:
        plt.savefig("imageshift-Case{0}.{1}".format(Case["id"], filetype))
# modified by Eric Firing
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm

imgData = [[1.0/x + 1.0/y for x in range(1,10)] for y in range(1,10)]

tMin = 1000000066 # Shifted by half
tMin = 100000066  # Blank!
tMin = 10000066   # OK
tMax = tMin+10

# The following combination is also shifted by half
tMin = 10000066.1  # adding the 0.1 doesn't make a difference
tMax = tMin+1

print tMin, tMax

axImg=plt.subplot(111)
axImg.imshow(imgData, norm=LogNorm(), extent=(tMin,tMax,1,10),
                 interpolation='none', origin="upper")
axImg.set_aspect('auto')

plt.savefig("imageshift.png")
plt.savefig("imageshift.eps")
plt.savefig("imageshift.pdf")
@mdboom mdboom was assigned Aug 14, 2012
@mdboom
Matplotlib Developers member

Watch what happens when you do:

Case3={"id":3,
       "interp":"none",
       "extents":(1,200,1,100)}

the image is shrunk horizontally and centered, when it should remain fully covering the axes. I think the bug is most likely in _draw_unsampled_image not taking into account non-real extents, but I don't yet have a solution. @leejjoon, I believe, is the original author of that code -- maybe he has some idea.

@pwuertz

That Case3 looks like your viewer is trying to keep the aspect ratio somehow. Indeed this here fixes the problem for my default SVG gnome viewer (so cairo in general?):

diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py
index 0b9b7ab..a4b72c9 100644
--- a/lib/matplotlib/backends/backend_svg.py
+++ b/lib/matplotlib/backends/backend_svg.py
@@ -801,6 +801,7 @@ class RendererSVG(RendererBase):
                 y *= -1.0
             attrib[u'transform'] = generate_transform(
                 [(u'matrix', flipped)])
+            attrib['preserveAspectRatio'] = "none"
             self.writer.element(
                 u'image',
                 x=unicode(x), y=unicode(y),

The bug is still shows up in Firefox and Chrome. Also the original post states that this problem highly depends on the viewer :/

@pwuertz

And evaluating (x, y, height, width) in python instead of having the SVG viewers interpret the transformation fixes it for all my applications...

diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py
index 0b9b7ab..f300b4a 100644
--- a/lib/matplotlib/backends/backend_svg.py
+++ b/lib/matplotlib/backends/backend_svg.py
@@ -793,18 +793,14 @@ class RendererSVG(RendererBase):
                 width=unicode(w), height=unicode(h),
                 attrib=attrib)
         else:
-            flipped = self._make_flip_transform(transform)
-            flipped = np.array(flipped.to_values())
-            y = y+dy
-            if dy > 0.0:
-                flipped[3] *= -1.0
-                y *= -1.0
-            attrib[u'transform'] = generate_transform(
-                [(u'matrix', flipped)])
+            x, y = transform.transform_point((x,y))
+            scale_x, _, _, scale_y, _, _ = transform.to_values()
+            w, h = dx * scale_x, dy * scale_y
+            attrib['preserveAspectRatio'] = "none"
             self.writer.element(
                 u'image',
-                x=unicode(x), y=unicode(y),
-                width=unicode(dx), height=unicode(abs(dy)),
+                x=unicode(x), y=unicode(self.height-y-h),
+                width=unicode(w), height=unicode(h),
                 attrib=attrib)

         if url is not None:
@efiring
Matplotlib Developers member

I suspect the essence of this fix can and should be in image.py so that it doesn't have to be repeated in each backend.

@leejjoon
Matplotlib Developers member

With interpolation="none", we cannot avoid using the matrix in the backend, and I doubt if pwuertz's solution will work for rotated and/or skewed images.
On the other hand, I think what we should try is a similar approach so that the matrix only contains rotation and skewing (i.e., matplotlib pre-calculates translation and scaling), and I think we can do this in image.py as Eric suggested. I will try to give a more look later.

@pwuertz

It wouldn't have worked with any transformation but scaling, just wanted to share the observation that numerically the transformations shouldn't be doing stuff that exceeds a few orders of magnitudes as this seems to freak out some viewers.

@leejjoon
Matplotlib Developers member

I think above PR seem to resolve the issue. Please test it and see if it works.

@pelson
Matplotlib Developers member

@technobauble : Does the mentioned PR solve the issue for you?

@technobauble

This appears to solve the problem for me in both the test cases that I posted and the actual application that I am developing.

Thank you everyone for your time and the promptness of the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment