The Impossible Squish
The Impossible Squish

Reputation: 325

PIL library Image.fromarray() causes AttributeError: 'tuple' object has no attribute '__array_interface__'

I'm using the physics simulator pybullet, and want to save an image from a 'virtual camera' within my simulation, using the following two lines of code.

camera1 = pybullet.getCameraImage(900,600)
im1 = Image.fromarray(camera1[2])

The first line uses pybullet's getCameraImage function to return an unprocessed image. camera1[2] is a list of pixel colors in RGBA format, in range [0..255] for each color.
The second line should take that array and convert it into an image which I can then save and view.

When I run the code I get the following error message:

Traceback (most recent call last):
  File "generate_dataset.py", line 43, in <module>
    im1 = Image.fromarray(camera1[2], "RGBA")
  File "/usr/lib/python3/dist-packages/PIL/Image.py", line 2140, in 
fromarray
    arr = obj.__array_interface__
AttributeError: 'tuple' object has no attribute '__array_interface__'

The code was working yesterday on Ubuntu 14.04, but today I upgraded to Ubuntu 16.04 and the code has stopped working. I have tried running it with Python 2.7.12 and Python 3.5.2, both versions get the same error.

Things I have tried:
Adding another line to convert the list into a numpy array:
camera1 = p.getCameraImage(900,600) imarray = np.asarray(camera1[2]) im1 = Image.fromarray(imarray)
Resulted in:

Traceback (most recent call last):
  File "generate_dataset.py", line 42, in <module>
    im1 = Image.fromarray(imarray)
  File "/usr/local/lib/python2.7/dist-packages/PIL/Image.py", line 2431, in fromarray
    raise TypeError("Cannot handle this data type")
TypeError: Cannot handle this data type

Changing the last line to:
im1 = Image.fromarray(imarray.astype('uint8'))
Resulted in:

Traceback (most recent call last):
  File "generate_dataset.py", line 42, in <module>
    im1 = Image.fromarray(imarray.astype('uint8'))
  File "/usr/lib/python3/dist-packages/PIL/Image.py", line 2165, in fromarray
    size = shape[1], shape[0]
IndexError: tuple index out of range

Extra info, if needed

Pybullet documentation: https://docs.google.com/document/d/10sXEhzFRSnvFcl3XxNGhnD4N2SedqwdAvK3dsihxVUA/edit#heading=h.2ye70wns7io3

PIL documentation: https://pillow.readthedocs.io/en/3.1.x/reference/Image.html

My full code:

import pybullet as p
import pybullet_data
import time
from PIL import Image
from random import *
import numpy as np

nExamples = 200

for n in range(0, nExamples):

  print ("Running example " + str(n))

  physicsClient = p.connect(p.DIRECT) #or p.GUI for graphical version
  p.setAdditionalSearchPath(pybullet_data.getDataPath()) #optionally
  p.setGravity(0,0,-10)
  planeId = p.loadURDF("ground.urdf")
  p.resetDebugVisualizerCamera( cameraDistance=1, cameraYaw=0, cameraPitch=-30, cameraTargetPosition=[0,0,0])

  x1 = uniform(-0.03,0.03)
  y1 = uniform(-0.03,0.03)
  x2 = x1 + uniform(-0.03,0.03)
  y2 = y1 + uniform(-0.03,0.03)
  cubeStartPos = [0.0,0,0.025]
  cubeStartPos1 = [x1,y1,0.075]
  cubeStartPos2 = [x2,y2,0.125]
  yaw0 = uniform(0,np.pi/2)
  yaw1 = uniform(0,np.pi/2)
  yaw2 = uniform(0,np.pi/2)
  cubeStartOrientation0 = p.getQuaternionFromEuler([0,0,yaw0])
  cubeStartOrientation1 = p.getQuaternionFromEuler([0,0,yaw1])
  cubeStartOrientation2 = p.getQuaternionFromEuler([0,0,yaw2])
  boxId = p.loadURDF("red_block.urdf",cubeStartPos, cubeStartOrientation0)
  boxId1 = p.loadURDF("green_block.urdf",cubeStartPos1, cubeStartOrientation1)
  boxId2 = p.loadURDF("blue_block.urdf",cubeStartPos2, cubeStartOrientation2)

  #saving the initial image...
  camera1 = p.getCameraImage(900,600)
  imarray = np.asarray(camera1[2])
  im1 = Image.fromarray(imarray.astype('uint8'))

  for i in range (250):
    p.stepSimulation()
    time.sleep(1./20.)

  camera2 = p.getCameraImage(900,600)

  #saving the image after blocks movement --> if stable this image is equal to the initial...
  im2 = Image.fromarray(camera2[2])

  #Are the images different? (Is it unstable?) --> if yes then diff is large, otherwise, diff is negligible
  diff = (camera2[2] - camera1[2]).sum()
  print("DIFFERENCE =", diff)
  if abs(diff) < 100000:
    im1.save("images/stable/image_%d.png" % n)
  else:
    im1.save("images/unstable/image_%d.png" % n)

  #cropping images
  cropped = im1.crop((350,200,550,400))
  cropped.save("images/cropped/image_%d.png" % n)

  p.disconnect()

  print ("Reached end of loop\n")

Upvotes: 2

Views: 6448

Answers (2)

Prajot Kuvalekar
Prajot Kuvalekar

Reputation: 6618

Earlier answer is TLDR;
So i will tell the exact reason
This error is mostly because : In Image.fromarray(arr) you might have given tuple in place of argument:arr where it excepts a numpy array

Upvotes: 0

PM 2Ring
PM 2Ring

Reputation: 55469

It's not clear from your description whether camera1[2] is a flat list of consecutive R, G, B, A values, or whether it's a list of RGBA tuples. So I'll show you how to read both options. ;)

Your main problem is that your data doesn't contain width and height information, so we need to supply that info, somehow. One way to do that would be to read the data into a 3D Numpy array of the correct shape. But we can also do it directly in PIL by using the appropriate Image methods.

For my demonstrations I use Python loops to create some simple RGBA data.

This script creates a list of RGBA tuples.

from PIL import Image

maxval = 255
width, height = 400, 300

# Display size info
size = width * height
fmt = 'Width: {}, Height: {}, Pixels: {}, Bytes: {}'
print(fmt.format(width, height, size, size * 4))

# Make a 2D gradient that starts at black in the top left corner,
# with red & green increasing horizontally, blue increasing vertically.
# This would be much faster using Numpy instead of Python loops.
pixels = []
# Make all pixels fully opaque
alpha = maxval
for y in range(height):
    blu = maxval * y // height
    for x in range(width):
        red = gre = maxval * x // width
        # Make a single RGBA pixel as a tuple
        pix = red, gre, blu, alpha
        # And save it
        pixels.append(pix)

# Show that the size of `pixels` is correct and show the first few pixels
print('Size:', len(pixels))
print(pixels[:8])

# Make a new image object. All pixels are set to black. 
img = Image.new('RGBA', (width, height))
# Copy the pixel data to the Image
img.putdata(pixels)
img.show()
img.save('test1.png') 

output

Width: 400, Height: 300, Pixels: 120000, Bytes: 480000
Size: 120000
[(0, 0, 0, 255), (0, 0, 0, 255), (1, 1, 0, 255), (1, 1, 0, 255), (2, 2, 0, 255), (3, 3, 0, 255), (3, 3, 0, 255), (4, 4, 0, 255)]

test1.png

2D fade from black to yellow, blue, white


This script creates a flat list of R, G, B, A values. It uses a Python 3 bytes object, so it won't work properly on Python 2.

from PIL import Image

maxval = 255
width, height = 400, 300

# Display size info
size = width * height
fmt = 'Width: {}, Height: {}, Pixels: {}, Bytes: {}'
print(fmt.format(width, height, size, size * 4))

# Make a 2D gradient that starts at black in the top left corner,
# with red & green increasing horizontally, blue increasing vertically.
# This would be much faster using Numpy instead of Python loops.
rgba = []
# Make all pixels fully opaque
alpha = maxval
for y in range(height):
    blu = maxval * y // height
    for x in range(width):
        red = gre = maxval * x // width
        # Make a single RGBA pixel as a tuple
        pix = red, gre, blu, alpha
        # And save each of red, gre, blu, alpha to rgba. 
        # By using `.extend` we create a flat list
        rgba.extend(pix)

# Show that the size of `rgba` is correct and show the first few values.
print('Size:', len(rgba))
print(rgba[:32])

# Convert the rgba list to bytes.
rgba = bytes(rgba)
# Make a new image object from the bytes
img = Image.frombytes('RGBA', (width, height), rgba)
img.show()
img.save('test2.png')

output

Width: 400, Height: 300, Pixels: 120000, Bytes: 480000
Size: 480000
[0, 0, 0, 255, 0, 0, 0, 255, 1, 1, 0, 255, 1, 1, 0, 255, 2, 2, 0, 255, 3, 3, 0, 255, 3, 3, 0, 255, 4, 4, 0, 255]

The file 'test2.png' is identical to 'test1.png'.

Upvotes: 4

Related Questions