Creating Images with Numpy

posted August 6th 2007 at 1708 EDT in All, Programming, Python

Suppose you need to create images pixel by pixel, and they are large images, and you want to do it quickly... How do you do it? with PIL putpixel()? with wxImage SetRGB()? No, they are to slow, you need to use Numpy.

You might be ok with smaller images, but when you start generating images that are around 5000,5000 pixels large, speed starts to matter.

if setting each pixel individually using an image library is too slow, how can you do it? You build your image as an array and then load it into an image tool in bulk.

So whats the catch?

The catch is understanding how PIL's frombuffer() method works. It expects raw data. You can think of it as a single integer for each pixel. But how do you store all 4 color values (Red,Green,Blue + Alpha transparency) in a single integer value? You will need to do a little hex editing.

If you are familiar with writing colors as hex values (ie #FF0000 for red) you know that the first two characters are for red, the second two are for green and the last two are for blue. You could also think of that as the three separate hex values 0xFF 0x00 0x00.

In a python prompt it's easy to convert from Hex to Decimal


>>> 0xFF
255
>>> 0x00
0
>>> 0x80
128
 

We need to add another value for alpha transparency, as you can see above, 0x80 will give us 50% transparency.

Now, you can make a single integer out of those 4 values. The only trick is to now put them together in reverse order (apha,blue,green,red) or in the format 0xAABBGGRR).


>>> 0x800000FF
2147483903L
 

Now we have an integer which represents 50% transparent red to set in our numeric array.

Now we get to creating the array for our image, we only need to know what type of array to create. Our integers for a RGBA image are unsigned 32bit integers. If we were creating a RGB image we would use unsigned 16bit integers.


import numpy
w,h=1024,768 ## this is the size image we want to create
img = numpy.empty((w,h),numpy.uint32)
img.shape=h,w ## set the array shape to our image shape; yes i know it seems backwards, but it's not!
 

Now img is a numpy array we can use to set the pixels to whatever value we want. By default it is already a Black image with 100% alpha transparency which is convenient. It may seem odd to set the shape to height,width; and well, it is odd. I'm not quite sure why that seems backwards.

Here is how to set the top left pixel (the one at position 0,0) to our semi-transparent red color.


img[0,0]=0x800000FF
 

With numpy you can also use a fancy notation to set multiple contingent pixels to the same value. This is where we get a huge speed improvement over setting pixels individually. To set the right half of the image (pixels 512-1024) of rows 10-20 solid blue, we would do this:


img[10:21,512:1024]=0xFFFF0000
 

The generic format is img[startrow:finishrow+1,starx:endx]

Now a few more fun rows


img[21:401,758:769]=0xFFFF0000
img[401:411,512:769]=0xFFFF0000
 

The last step is to convert the array into an image and save it as a png image.


from PIL import Image
pilImage = Image.frombuffer('RGBA',(w,h),img,'raw','RGBA',0,1)
pilImage.save('my.png')
 

And there you have it

6 Responses

  1. #1 Cyclone31
    2 years, 6 months ago

    Here’s another way to accomplish what you wanted, using Image.fromarray instead of the cryptic looking frombuffer:

    import numpy
    w,h = 1024,768
    img = numpy.zeros((h,w,4), numpy.uint8)
    img[0,0]=[255,0,0,128]
    img[10:21,512:,2:4]=[255,255]
    img[21:401,758:769,2:4]=[255,255]
    img[401:411,512:769,2:4]=[255,255]

    from PIL import Image
    pilImage = Image.fromarray(img, ‘RGBA’)
    pilImage.save(’my.png’)

    This version also sets the array size to h x w x 4 so that you can set individual color components easily without the hex codes.

    As far as being backwards for dimensions, it’s because we think of cartesian as (x,y), but the arrays (like matrices) are dimmed as (nrows,cosl) which maps to (y,x) for images.

  2. #2 miska knapek
    2 years, 4 months ago

    great!
    this is just what I needed.
    just started messing with numpy, after spending a bit of time with PIL, and wondered how to speed things up.

    many thanks

    miska <- (dane, of czech parents, living in finland)

  3. #3 Chris Barker
    2 years, 2 months ago

    Another note:

    If you have a PIL image, and want to get a numpy array, you can use:

    arr = numpy.asarray(PIL_image)

    that will create an array that shares the PIL_image data buffer.

  4. #4 Joe
    11 months, 3 weeks ago

    Hi,
    Thanks for the above posts!
    I’m trying to use the code above to get images from numpy arrays. All images are greyscale. I keep getting regularly spaced dots, not images.
    Here’s a simple script that illustrates the problem. If anyone knows how to fix this I’d be very much obliged. Thank you!
    joe

    #

    import numpy
    from Tkinter import *
    import Image, ImageTk, ImageDraw

    window = Tk()
    canvas = Canvas(window, width = 512, height = 512, bg = ‘blue’)

    joeMat = numpy.zeros([512,512])

    for i in xrange (200):
    for j in xrange (200):
    joeMat[i,j] = 200

    pilImage = Image.fromarray(joeMat,’L')
    photo = ImageTk.PhotoImage(pilImage)
    canvas.create_image(256,256,image = photo)

    canvas.pack()
    mainloop()

  5. #5 Ju-D
    10 months, 1 week ago

    joeMat = numpy.zeros([512,512])

    … needs to be:

    joeMat = numpy.zeros([512,512], numpy.uint8)

  6. #6 jono
    3 months, 3 weeks ago

    joeMat = numpy.zeros([512,512], numpy.uint8)

    Or

    joeMat = numpy.zeros([512,512], dtype="uint8")
    

    Don’t do this
    for i in xrange (200):
    for j in xrange (200):
    joeMat[i,j] = 200

    Even if you are using xrange, it is terribly slow. There’s a section in the numpy docs on how to iterate through an array much faster. Can’t think of it off hand.