Prenons ce cas d'école où on est en possession d'une large quantité de photographies que l'on doit traiter. Dans mon cas, ces photos sont prises avec un Nikon D300 dans un format tif. Je prends une image par minutes sur 7 à 48 heures. Le script présenté ici ne fait qu'un découpage des photos sur une zone d'intérêt et les enregistre sous un format png. D'autres actions peuvent être ensuite enchainées, selon les besoins. Le code est donné en fin d'article, son contenu me semblait suffisamment intéressant pour en faire une billet.

Le "crop" se fait avec la bibliothèque PIL. Puisqu'elle n'est disponible qu'en python2, on écrira le script dans cette version. Cette partie est réalisée par la fonction crop_pic()

L'étape suivante est de récupérer la liste des images à traiter et le répertoire de destination. J'utilise la bibliothèque argparse même si quelque chose de plus léger comme cliz pouvait suffire. Ainsi, je spécifie un répertoire source et un répertoire de destination.

Dans le répertoire source, je récupère à l'aide de glob la liste des fichiers. On pourrait être plus restrictifs en imposant une extension tif. Le nommage est plus ou moins complexe, mais il comporte au moins un numéro. La liste des fichiers est donc triée avec sorted (Vous allez comprendre dans un instant pourquoi). Il faut faire attention, car si les fichiers sources comportent des numéros comme 1, 2, ... 11, 12, ..., un tri sur cette liste mettra 11 avant 2. On spécifie donc une clé pour avoir un tri naturel. J'ai dû récupérer ce bout de code un jour sur stack overflow.

Une fois ceci fait, il faut générer de nouveaux noms, le nombre se référant au temps. J'ai choisi d'écrire un générateur dont je peux spécifier la première valeur. Mes numéros sont sous la forme 00001 pour avoir un tri naturel avec un ls par ex. :) On pourrait spécifier un pas dans le cas où la période d'acquisition aurait été différente de 1 minute pour avoir ainsi un indicateur temporelle correct dans le nom.

On passe enfin à l'action en lançant les crop sur chaque image. Le processus étant un peu lent avec une alternance de charge processeur et d'IO, il est plus efficace de traiter des images en parallèle. Pour cela, j'ai choisi d'utiliser une pool de tâches. On la remplit, et une fois vidé, on admire nos belles images traitées.

#!/usr/bin/env python2
# -*- coding: utf-8 -*-


from PIL import Image

import glob
import os
from multiprocessing import Pool

import re
import argparse


def get_name(path, start=1):
    """
    Generator for pictures
    :param path: dir path for the picture
    :param start: first number
    :returns: filename
    """
    i = start
    while True:
        pngfile = os.path.join(path, str(i).zfill(5) + '.png')
        yield(pngfile)
        i += 1

def crop_pic(filename, output, box):
    """
    :param filename: name of the picture
    :param output: destination file
    :param box: crop box (tuple)  (x_1,y_1,x_2,y_2)
    """
    print(" Processing... %s \n\t to... %s" % (filename, output))
    basename, ext = os.path.splitext(filename)
    name, ext = os.path.splitext(filename)
    im = Image.open(filename)
    region = im.crop(box)
    region.save(output)


def tryint(s):
    try:
        return int(s)
    except:
        return s

def alphanum_key(s):
    """
    Turn a string into a list of string and number chunks.
    >>> alphanum_key("z23a")
    ['z', 23, 'a']
    """
    return [ tryint(c) for c in re.split('([0-9]+)', s) ]


if __name__ == '__main__':

    box = (1130,220,3600,2450)
    start = 1

    #Arg parsing
    parser = argparse.ArgumentParser(description='Crop!', epilog='')
    parser.add_argument('-s', help='Source dir', metavar='SOURCE', required=True)
    parser.add_argument('-d', help='Dest dir', metavar='DEST', required=True)
    args = parser.parse_args()

    #Dirs
    cwd = os.getcwd()
    source_dir = os.path.join(cwd, args.s)
    dest_dir = os.path.join(cwd, args.d)
    os.makedirs(dest_dir)

    #List of pictures
    pic_list = glob.glob(source_dir + '/*')
    pic_list = sorted(pic_list, key=alphanum_key)

    #Create a pool
    pool = Pool(processes=4)
    generator = get_name(dest_dir, start)
    for im in pic_list:
        output = generator.next()
        pool.apply_async(crop_pic, args = (im, output, box, ))

    pool.close()
    pool.join()