Generating Image from Dictionary using PIL
As Martino says, it's putpixel()
okay for you to change a few random pixels, but it's not as efficient for plotting entire images. My approach is similar to his, except that I use a list of ints and .putdata()
. Here's some code to test these three different approaches.
from PIL import Image
from random import seed, randint
width, height = 200, 200
background = 0
seed(42)
d = dict(((x, y), randint(0, 255)) for x in range(width) for y in range(height))
algorithm = 2
print('Algorithm', algorithm)
if algorithm == 0:
im = Image.new('L', (width, height))
for i in d:
im.putpixel(i, d[i])
elif algorithm == 1:
buff = bytearray((background for _ in xrange(width * height)))
for (x,y), v in d.items():
buff[y*width + x] = v
im = Image.frombytes('L', (width,height), str(buff))
elif algorithm == 2:
data = [background] * width * height
for i in d:
x, y = i
data[x + y * width] = d[i]
im = Image.new('L', (width, height))
im.putdata(data)
#im.show()
fname = 'qrand%d.png' % algorithm
im.save(fname)
print(fname, 'saved')
Here are the typical timings on my 2GHz machine running Python 2.6.6
$ time ./qtest.py Algorithm 0 qrand0.png saved real 0m0.926s user 0m0.768s sys 0m0.040s $ time ./qtest.py Algorithm 1 qrand1.png saved real 0m0.733s user 0m0.548s sys 0m0.020s $ time ./qtest.py Algorithm 2 qrand2.png saved real 0m0.638s user 0m0.520s sys 0m0.032s
source to share
You can try image.putpixel()
to change the color of the pixel at a specific position. Sample code -
from PIL import Image
from random import randint
d = {(x,y):randint(0,255) for x in range(200) for y in range(200)}
im = Image.new('L',(200,200))
for i in d:
im.putpixel(i,d[i])
im.save('blah.png')
This gave me the output as -
source to share
You can do this with putpixel()
, but this could potentially involve tens of thousands of calls. How important this is depends on how many coordinate tuples are defined in the dictionary. I've included the method shown in each of the current answers for comparison (including my own, before any benchmarking was added, but for now I've made a small change in how it initializes the data buffer which made it noticeably faster).
To create a level playing field, for testing purposes, the input dictionary randomly selects only 1/2 of the possible pixels in the image to define and allows the rest to be set to the default color background
. Anand S Kumar's answer does not currently do the latter, but the slightly modified version shown below does.
Everyone produces the same image from the data.
from __future__ import print_function
import sys
from textwrap import dedent
import timeit
N = 100 # number of executions of each algorithm
R = 3 # number of repeations of executions
# common setup for all algorithms - is not included in algorithm timing
setup = dedent("""
from random import randint, sample, seed
from PIL import Image
seed(42)
background = 0 # default color of pixels not defined in dictionary
width, height = 200, 200
# create test dict of input data defining half of the pixel coords in image
coords = sample([(x,y) for x in xrange(width) for y in xrange(height)],
width * height // 2)
d = {coord: randint(0, 255) for coord in coords}
""")
algorithms = {
"Anand S Kumar": dedent("""
im = Image.new('L', (width, height), color=background) # set bgrd
for i in d:
im.putpixel(i, d[i])
"""),
"martineau": dedent("""
data = bytearray([background] * width * height)
for (x, y), v in d.iteritems():
data[x + y*width] = v
im = Image.frombytes('L', (width, height), str(data))
"""),
"PM 2Ring": dedent("""
data = [background] * width * height
for i in d:
x, y = i
data[x + y * width] = d[i]
im = Image.new('L', (width, height))
im.putdata(data)
"""),
}
# execute and time algorithms, collecting results
timings = [
(label,
min(timeit.repeat(algorithms[label], setup=setup, repeat=R, number=N)),
) for label in algorithms
]
print('fastest to slowest execution speeds (Python {}.{}.{})\n'.format(
*sys.version_info[:3]),
' ({:,d} executions, best of {:d} repetitions)\n'.format(N, R))
longest = max(len(timing[0]) for timing in timings) # length of longest label
ranked = sorted(timings, key=lambda t: t[1]) # ascending sort by execution time
fastest = ranked[0][1]
for timing in ranked:
print("{:>{width}} : {:9.6f} secs, rel speed {:4.2f}x, {:6.2f}% slower".
format(timing[0], timing[1], round(timing[1]/fastest, 2),
round((timing[1]/fastest - 1) * 100, 2), width=longest))
Output:
fastest to slowest execution speeds (Python 2.7.10) (100 executions, best of 3 repetitions) martineau : 0.255203 secs, rel speed 1.00x, 0.00% slower PM 2Ring : 0.307024 secs, rel speed 1.20x, 20.31% slower Anand S Kumar : 1.835997 secs, rel speed 7.19x, 619.43% slower
source to share