Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I am using the Python Imaging Library to colorize a black and white image with a lookup table that defines the color relationships. The lookup table is simply a 256-element list of RGB tuples:

>>> len(colors)
>>> colors[0]
(255, 237, 237)
>>> colors[127]
(50, 196, 33)

My first version used the getpixel() and putpixel() methods:

    for x in range(w):
        for y in range(h):
            pix = img.getpixel((x,y))
            img.putpixel((x,y), colors[pix[0]])

This was horribly slow. A profile report pointed to the putpixel and getpixel methods as the culprits. A little investigation (i.e, read the docs) and I find "Note that this method is relatively slow." re: putpixel. (actual runtime: 53s in putpixel and 50s getpixel for a 1024x1024 image)

Based on the suggestion in the docs, I used im.load() and direct pixel access instead:

    pixels = img.load()
    for x in range(w):
        for y in range(h):
            pix = pixels[x, y]
            pixels[x, y] = colors[pix[0]]                

Processing sped up by an order of magnitude, but is still slow: about 3.5s to process a 1024x1024 image.

A more thorough study of the PIL docs seems to indicate Image.point() is exactly intended for this purpose:

im.point(table) => image

im.point(function) => image

Returns a copy of the image where each pixel has been mapped through the given table. The table should contains 256 values per band in the image. If a function is used instead, it should take a single argument. The function is called once for each possible pixel value, and the resulting table is applied to all bands of the image.

I've spent some time hacking around with the interface, but can't quite seem to get it right. Forgive my ignorance, but PIL's docs are curt and I don't have much image processing experience. I've googled around a bit and turned up a few examples, but nothing that made the usage "click" for me. Thus, finally, my questions:

  • Is Image.point() the right tool for this job?
  • What format/structure does Image.point() expect the table?
  • Can someone rough out an example implementation? Every iteration I've tried so far has ended up with a straight black image.
  • You should flatten the list so instead of [(12, 140, 10), (10, 100, 200), ...] use:

    [12, 140, 10, 10, 100, 200, ...]
    

    Here is a quick example I just tried:

    im = im.point(range(256, 0, -1) * 3)
    

    And by the way, if you need more control over colors and you feel Image.point is not for you you can also use Image.getdata and Image.putdata to change colors more quickly than both load and putpixel. It is slower than Image.point though.

    Image.getdata gives you the list of all pixels, modify them and write them back using Image.putdata. It is that simple. But try to do it using Image.point first.

    I made a mistake in the first explanation, I'll explain correctly now:

    The color table actually is like this

    [0, 1, 2, 3, 4, 5, ...255, 0, 1, 2, 3, ....255, 0, 1, 2, 3, ...255]
    

    Each band range next to the other. To change the color (0, 0, 0) to (10, 100, 10) it need to become like this:

    [10, 1, 2, 3, 4, 5, ...255, 100, 1, 2, 3, ....255, 10, 1, 2, 3, ...255]
    

    To transform your color list into the right format try this:

    table = sum(zip(*colors), ())
    

    I think my first example should demonstrate the formate for you.

    flatten the list? ok, but how does that work? I want pixels with value 0 -> (12, 140, 10) and pixels with value 255 -> (254, 237, 220). – J.J. Feb 2, 2010 at 1:42 you know, I think this might be The Only Place On the Internet where the expected format of that table is described. // I just got img.point() working with the lookup table, thanks to your description. The results aren't exactly what I expected, but I've got enough to hack around on and figure it out. Thanks so much! – J.J. Feb 2, 2010 at 2:02 @J.J., The format is not really obvious. I made a mistake in my first explanation and I fixed it now. I hope I got it right this time. I'm going to sleep so I won't be able to answer questions. – Nadia Alramli Feb 2, 2010 at 2:15 ah, yes! like a champ, and in 0.05s runtime inside point()! thanks, too for the pythonic ways to flatten the lists. Those techniques alone would've taken me a while to puzzle out. – J.J. Feb 2, 2010 at 2:35 If this really IS the format of the color table, then it's of pretty limited use. For the example at hand, it doesn't just change (0,0,0) pixels, it changes any pixel with red = 0, OR green = 0 OR blue = 0. In short, it's only suited for manipulating the bands as three (or four) individual separate data sets, ignorant of the actual color of each pixel. – gwideman Oct 23, 2014 at 21:03

    I think it might be more typical to point on a band-by-band basis like so (lifted directly from the PIL tutorial):

    # split the image into individual bands
    source = im.split()
    R, G, B = 0, 1, 2
    # select regions where red is less than 100
    mask = source[R].point(lambda i: i < 100 and 255)
    # process the green band
    out = source[G].point(lambda i: i * 0.7)
    # paste the processed band back, but only where red was < 100
    source[G].paste(out, None, mask)
    # build a new multiband image
    im = Image.merge(im.mode, source)
                    the table format makes more sense with a single band, that's for sure.   I may have to use this route in order to handle the alpha channel with a special case...
    – J.J.
                    Feb 2, 2010 at 2:36
            

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.