Search code examples
pythonnumpypython-imaging-libraryattributeerror

What is causing dimension-dependent AttributeError in PIL fromarray function?


I'm getting an error from the following Python3 code, in the indicated lines. x, y, and z are all plain 2D numpy arrays identical but for size, and ought to work the same. Yet they act differently, with y and z crashing while x works fine.

import numpy as np
from PIL import Image

a = np.ones( ( 3,3,3), dtype='uint8' )
x = a[1,:,:]
y = a[:,1,:]
z = a[:,:,1]

imx = Image.fromarray(x)  # ok
imy = Image.fromarray(y)  # error
imz = Image.fromarray(z)  # error

but this works

z1 = 1*z
imz = Image.fromarray(z1)   # ok

The error is:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python3\lib\site-packages\PIL\Image.py", line 1918, in fromarray
    obj = obj.tobytes()
AttributeError: 'numpy.ndarray' object has no attribute 'tobytes'

So what's different between x, y, z, z1? Nothing that I can tell.

>>> z.dtype
dtype('uint8')
>>> z1.dtype
dtype('uint8')
>>> z.shape
(3, 4)
>>> z1.shape
(3, 4)

I'm using Python 3.2.3 on a Windows 7 Enterprise machine, with everything 64 bit.


Solution

  • I can reproduce on ubuntu 12.04 with Python 3.2.3, numpy 1.6.1, and PIL 1.1.7-for-Python 3 at http://www.lfd.uci.edu/~gohlke/pythonlibs/#pil. The difference happens because the array_interface of x doesn't have a strides value but y's and z's do:

    >>> x.__array_interface__['strides']
    >>> y.__array_interface__['strides']
    (9, 1)
    >>> z.__array_interface__['strides']
    (9, 3)
    

    and so a different branch is taken here:

    if strides is not None:
        obj = obj.tobytes()
    

    The documentation mentions tostring, not tobytes:

    # If obj is not contiguous, then the tostring method is called
    # and {@link frombuffer} is used.
    

    And the Python 2 source of PIL 1.1.7 uses tostring:

    if strides is not None:
        obj = obj.tostring()
    

    so I suspect that this was an error introduced during a 2-to-3 conversion in which str/bytes changes were made. Simply replace tobytes() by tostring() in Image.py and it should work:

    Python 3.2.3 (default, May  3 2012, 15:54:42) 
    [GCC 4.6.3] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import numpy as np
    >>> from PIL import Image
    >>> 
    >>> a = np.ones( ( 3,3,3), dtype='uint8' )
    >>> x = a[1,:,:]
    >>> y = a[:,1,:]
    >>> z = a[:,:,1]
    >>> 
    >>> imx = Image.fromarray(x)  # ok
    >>> imy = Image.fromarray(y)  # now no error!
    >>> imz = Image.fromarray(z)  # now no error!
    >>>