Asynchronously read and process an image in python

The multiprocessing package is pretty easy to use. Look at the Queues example for a guide. You'll be following the producer consumer model. You want one (or more) producer processes reading images, and one (or more) consumer processes doing the image processing.

Your example would look something like this:

from multiprocessing import Process, Queue
import scipy

def process_images(q):
    while not q.empty():
        im = q.get()
        # Do stuff with item from queue

def read_images(q, files):
    for f in files:
        q.put(scipy.misc.imread(f))

if __name__ == '__main__':
    q = Queue()

    producer = Process(target=read_images, args=(q, files))
    producer.start()
    consumer = Process(target=process_images, args=(q, ))
    consumer.start()

This is a bit simpler than your original idea. In this example the producer adds to the queue as fast as it can rather than just staying one ahead of the consumer. That might be a problem if the producer gets so far ahead that you don't have enough memory to hold the queue. If problems arise you can get deeper into the multiprocessing docs, but this should be enough to get you started.


Philip's answer is good, but will only create a couple of processes (one reading, one computing) which will hardly max out a modern >2 core system. Here's an alternative using multiprocessing.Pool (specifically, its map method) which creates processes which do both the reading and compute aspects, but which should make better use of all the cores you have available (assuming there are more files than cores).

#!/usr/bin/env python

import multiprocessing
import scipy
import scipy.misc
import scipy.ndimage

class Processor:
    def __init__(self,threshold):
        self._threshold=threshold

    def __call__(self,filename):
        im = scipy.misc.imread(filename)
        label,n = scipy.ndimage.label(im > self._threshold)
        return n

def main():
    scipy.misc.imsave("lena.png", scipy.misc.lena())
    files = ['lena.png'] * 100

    proc=Processor(128)
    pool=multiprocessing.Pool()
    results=pool.map(proc,files)

    print results

if __name__ == "__main__":
    main()

If I increase the number of images to 500, and use the processes=N argument to Pool, then I get

Processes   Runtime
   1         6.2s
   2         3.2s
   4         1.8s
   8         1.5s

on my quad-core hyperthreaded i7.

If you got into more realistic use-cases (ie actual different images), your processes might be spending more time waiting on the image data to load from storage (in my testing, they load virtually instantaneously from cached disk) and then it might be worth explicitly creating more processes than cores to get some more overlap of compute and load. Only your own scalability testing on a realistic load and HW can tell you what's actually best for you though.