Thanks to visit codestin.com
Credit goes to github.com

Skip to content

imsave reduces 1row from the image #4280

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

Closed
kushal124 opened this issue Mar 26, 2015 · 8 comments
Closed

imsave reduces 1row from the image #4280

kushal124 opened this issue Mar 26, 2015 · 8 comments

Comments

@kushal124
Copy link

Steps to reproduce :

im= np.zeros((481,321))
plt.imsave('./test',im)

img = plt.imread('test.png')

img.shape[:2] != im.shape

img.shape is (480,321)

It removes one row !

@tacaswell
Copy link
Member

08:41 $ identify -verbose test.png
Image: test.png
  Format: PNG (Portable Network Graphics)
  Mime type: image/png
  Class: DirectClass
  Geometry: 321x480+0+0
  Resolution: 39.37x39.37
  Print size: 8.15342x12.192
  Units: PixelsPerCentimeter
  Type: PaletteAlpha
  Endianess: Undefined
  Colorspace: sRGB
  Depth: 8-bit
  Channel depth:
    red: 1-bit
    green: 1-bit
    blue: 8-bit
    alpha: 1-bit
  Channel statistics:
    Pixels: 154080
    Red:
      min: 0 (0)
      max: 0 (0)
      mean: 0 (0)
      standard deviation: 0 (0)
      kurtosis: 0
      skewness: 0
    Green:
      min: 0 (0)
      max: 0 (0)
      mean: 0 (0)
      standard deviation: 0 (0)
      kurtosis: 0
      skewness: 0
    Blue:
      min: 127 (0.498039)
      max: 127 (0.498039)
      mean: 127 (0.498039)
      standard deviation: 0 (0)
      kurtosis: 0
      skewness: 0
    Alpha:
      min: 255 (1)
      max: 255 (1)
      mean: 255 (1)
      standard deviation: 0 (0)
      kurtosis: 0
      skewness: 0
  Image statistics:
    Overall:
      min: 0 (0)
      max: 127 (0.498039)
      mean: 31.75 (0.12451)
      standard deviation: 0 (0)
      kurtosis: 0
      skewness: 0
  Colors: 1
  Histogram:
    154080: (  0,  0,127,255) #00007F srgba(0,0,127,1)
  Rendering intent: Perceptual
  Gamma: 0.454545
  Chromaticity:
    red primary: (0.64,0.33)
    green primary: (0.3,0.6)
    blue primary: (0.15,0.06)
    white point: (0.3127,0.329)
  Background color: white
  Border color: srgba(223,223,223,1)
  Matte color: grey74
  Transparent color: none
  Interlace: None
  Intensity: Undefined
  Compose: Over
  Page geometry: 321x480+0+0
  Dispose: Undefined
  Iterations: 0
  Compression: Zip
  Orientation: Undefined
  Properties:
    date:create: 2015-03-26T08:39:30-04:00
    date:modify: 2015-03-26T08:39:30-04:00
    png:IHDR.bit-depth-orig: 8
    png:IHDR.bit_depth: 8
    png:IHDR.color-type-orig: 6
    png:IHDR.color_type: 6 (RGBA)
    png:IHDR.interlace_method: 0 (Not interlaced)
    png:IHDR.width,height: 321, 480
    png:pHYs: x_res=3937, y_res=3937, units=1
    png:sRGB: intent=0 (Perceptual Intent)
    signature: b6dbfbc08558e862914167ad7152fc413de11c3100fa5a21c2e3b3f380eeeeb3
  Artifacts:
    filename: test.png
    verbose: true
  Tainted: False
  Filesize: 1.68KB
  Number pixels: 154K
  Pixels per second: 15.41MB
  User time: 0.000u
  Elapsed time: 0:01.009
  Version: ImageMagick 6.8.9-9 Q16 x86_64 2015-01-06 http://www.imagemagick.org

The issue is on the write side.

@tacaswell tacaswell added this to the next point release milestone Mar 26, 2015
@tacaswell
Copy link
Member

This looks like a float rounding issue.

If you look at how imsave is implemented (https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/image.py#L1270) it creates a figure sized at arr.size / dpi which should then turn back into a arr.size output, but it is not round tripping correctly:

In [16]: 100 * (481 / 100)
Out[16]: 480.99999999999994

This only happens at specific image sizes:

tst = [j - 100 * (j / 100) for j in range(1000)]
fig, ax = plt.subplots()
ax.plot(tst)
ax.set_ylabel('$j - (100 * (j / 100))$')
ax.set_xlabel('$j$')

so

The solution is to put a round call into where ever we compute the final pixel size instead of what I assume is currently an (implicit) floor.

@mfitzp
Copy link
Member

mfitzp commented Mar 26, 2015

This only occurs with specific image dimensions (e.g. 481,321). It's seems fine at other sizes nearby:

>>> im= np.zeros((479,321))
>>> im.shape[:2]
(479, 321)
>>> plt.imsave('./test',im)
>>> img = plt.imread('test.png')
>>> img.shape[:2]
(479, 321)

>>> im= np.zeros((482,321))
>>> im.shape[:2]
(482, 321)
>>> plt.imsave('./test',im)
>>> img = plt.imread('test.png')
>>> img.shape[:2]
(482, 321)

>>> im= np.zeros((481,321))
>>> im.shape[:2]
(481, 321)
>>> plt.imsave('./test',im)
>>> img = plt.imread('test.png')
>>> img.shape[:2]
(480, 321)

>>> im= np.zeros((480,321))
>>> im.shape[:2]
(480, 321)
>>> plt.imsave('./test',im)
>>> img = plt.imread('test.png')
>>> img.shape[:2]
(480, 321)

>>> im= np.zeros((479,321))
>>> im.shape[:2]
(479, 321)
>>> plt.imsave('./test',im)
>>> img = plt.imread('test.png')
>>> img.shape[:2]
(479, 321)
>>> 

Stepping through the execution of imsave() the following occurs:

[x / float(dpi) for x in (arr.shape[1], arr.shape[0])]  #  [3.21, 4.81]
fig = Figure(figsize=figsize, dpi=dpi, frameon=False)
canvas = FigureCanvas(fig)
im = fig.figimage(arr, cmap=cmap, vmin=vmin, vmax=vmax, origin=origin)
im.get_size()  #  (481, 321)
im.get_extent()  #  (-0.5, 320.5, -0.5, 480.5)

Following all the way into backend_agg.print_png we find the values still correct:

self.figure.get_window_extent()
TransformedBbox(Bbox([[0.0, 0.0], [3.21, 4.81]]), Affine2D(array([[ 100.,    0.,    0.], [   0.,  100.,    0.], [   0.,    0.,    1.]])))

Replacing the output function to use PIL as a backend has no effect:

#_png.write_png(renderer._renderer, filename_or_obj, self.figure.dpi)
buf, size = self.print_to_buffer()
image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
return image.save(filename_or_obj, format='png')

img.shape[:2]
(480, 321)

As print_to_buffer uses renderer._renderer this suggests the problem is there. The renderer is being passed the correct values:

renderer = self.get_renderer()
renderer, renderer.width, renderer.height   # <matplotlib.backends.backend_agg.RendererAgg object at 0x10b630850> 321.0 481.0

So the fault appears to be in .buffer_rgba() which I think is in the C library _backend_agg.so.

@mfitzp
Copy link
Member

mfitzp commented Mar 26, 2015

I think I've found the culprit in backend_agg.py print_to_png (line 527):

renderer, renderer.width, renderer.height, int(renderer.width), int(renderer.height)  

Returns:

<matplotlib.backends.backend_agg.RendererAgg object at 0x1091c7850> 321.0 481.0 321 480

The int() cast is magically turning 481.0 into 480. I am at a loss why that is happening but there it is. Note, numpy.floor also converts this 481.0 value into 480.

(Edit: changed the line/function above, I was working on my debug version through PIL)

@WeatherGod
Copy link
Member

I bet you that that 481.0 is merely the display result of something that is
really 480.99999999999.

On Thu, Mar 26, 2015 at 9:37 AM, Martin Fitzpatrick <
[email protected]> wrote:

I think I've found the culprit in backend_agg.py print_to_buffer (line 549
):

renderer, renderer.width, renderer.height, int(renderer.width), int(renderer.height)

Returns:

<matplotlib.backends.backend_agg.RendererAgg object at 0x1091c7850> 321.0 481.0 321 480

The int() cast is magically turning 481.0 into 480. I am at a loss why
that is happening but there it is.


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

@mfitzp
Copy link
Member

mfitzp commented Mar 26, 2015

@WeatherGod definitely, was forgetting that those really really small values showed nothing. Modifying get_renderer in backend_agg as follows solves this:

        self.renderer = RendererAgg(round(w), round(h), self.figure.dpi)

Not sure if there are any potential negative side effects here though.

@f0k
Copy link
Contributor

f0k commented May 3, 2015

Got trapped by the same bug. For anybody looking for a quick solution, a way around this is to set dpi=1:

In [16]: plt.imsave('foo.png', np.random.rand(115,100))
In [17]: Image.open('foo.png').size[::-1]
Out[17]: (114, 100)
In [18]: plt.imsave('foo.png', np.random.rand(115,100), dpi=1)
In [19]: Image.open('foo.png').size[::-1]
Out[19]: (115, 100)

Note that PNGs of dpi=1 will not play well with pdflatex. That's why the dpi setting was introduced in the first place, but possibly not carefully enough. A way around that is to either set the dpi to the greatest common divisor of height and width, or, if that is still too small, strip the dpi from the resulting PNG afterwards.

tacaswell added a commit to tacaswell/matplotlib that referenced this issue May 5, 2015
Round images sizes to the nearest integer rather than clipping when
passing down to the c++ layer.  This is mostly a problem when using
imsave/figimage to render an image 1:1 and is a result of float
precision issues when computing number of pixels from inches/dpi.

Closes matplotlib#4280
@tacaswell tacaswell modified the milestones: proposed next point release, next point release Jul 16, 2015
@tacaswell
Copy link
Member

Punting as this requires changes in the Agg layer and will not get done before the 1.5 release.

mdboom added a commit to mdboom/matplotlib that referenced this issue Jan 5, 2016
mdboom added a commit to mdboom/matplotlib that referenced this issue Jan 8, 2016
mdboom added a commit to mdboom/matplotlib that referenced this issue Jan 25, 2016
mdboom added a commit to mdboom/matplotlib that referenced this issue Jan 27, 2016
mdboom added a commit to mdboom/matplotlib that referenced this issue Jan 28, 2016
mdboom added a commit to mdboom/matplotlib that referenced this issue Feb 9, 2016
@QuLogic QuLogic modified the milestones: 2.0 (style change major release), 2.1 (next point release) Feb 18, 2016
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

6 participants