Décomposition des six faces du cube en mosaïque d’images

10 décembre 2009 - Mots-clés : Liens Linux Photographie

Décomposition six faces du cube en une mosaïque de tuiles

Cette décomposition en tuiles est nécessaire pour la visualisation du panorama sphérique par un visualisateur prenant en charge ce format. Un tel exemple de visualisateur est Pannellum que j'utilise pour mon site.

L'outil fourni avec Pannellum effectue la conversion de l'image équirectangulaire en tuiles. Or, pour effectuer les retouches du nadir, je suis obligé de convertir l'image équirectangulaire en faces de cube. J'ai donc trouvé naturel de pouvoir partir des faces de cube pour les convertir directement en tuiles. Ce n'est pas l'avis de l'auteur de Pannellum, j'ai soumis une merge request qui a été refusée. J'ai donc dû créer mon propre script de génération des tuiles à partir les faces de cube pour générer les tuiles. Le voici ci-dessous :

#!/usr/bin/env python3

import argparse
from PIL import Image
import os
import sys
import math
import subprocess

# Face order: front, back, up, down, left, right
faceLetters = ['f', 'b', 'u', 'd', 'l', 'r']

# Parse input
parser = argparse.ArgumentParser(description='Generate a Pannellum multires tile set from cube projections.',
                                 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('input', metavar='INPUT', help='Directory of cube faces')
parser.add_argument('-o', '--output', dest='output', default='./tiles',
                    help='output directory')
parser.add_argument('-s', '--tilesize', dest='tileSize', default=512, type=int,
                    help='tile size in pixels')
parser.add_argument('-c', '--cubesize', dest='cubeSize', default=0, type=int,
                    help='cube size in pixels, or 0 to retain all details')
parser.add_argument('-q', '--quality', dest='quality', default=75, type=int,
                    help='output JPEG quality 0-100')
parser.add_argument('--png', action='store_true',
                    help='output PNG tiles instead of JPEG tiles')
args = parser.parse_args()

# Create output directory
os.makedirs(args.output)

# Process input image information
print('Processing input image information...')
if os.path.isdir(args.input):
    inputDir = args.input
    removeFaces = False
    # check input arguments
    faces = [f for f in os.listdir(inputDir) if os.path.isfile(os.path.join(args.input, f))]
    if len(faces) != 6:
        print('Error: the number of faces is different than 6.')
        sys.exit(1)
    cubeSize = -1
    for f in faces:
        f = os.path.join(args.input, f)
        with Image.open(f) as image:
            w, h = image.size
            if w != h:
                print('Error: the face %s is not a square.' % f)
                sys.exit(1)
            if cubeSize == -1:
               cubeSize = w
            elif w != cubeSize:
                print("Error: all faces don't have the same size.")
                sys.exit(1)
    # sort image names, it is assumed that the first letter is in "fbudlr"
    faces = sorted(faces, key=lambda f: faceLetters.index(f[0]))
else:
    print('Error: the argument must be a directory.')
    sys.exit(1)

levels = int(math.ceil(math.log(float(cubeSize) / args.tileSize, 2))) + 1

extension = '.jpg'
if args.png:
    extension = '.png'

# Generate tiles
print('Generating tiles...')
for f in range(0, 6):
    size = cubeSize
    face = Image.open(os.path.join(inputDir, faces[f]))
    for level in range(levels, 0, -1):
        if not os.path.exists(os.path.join(args.output, str(level))):
            os.makedirs(os.path.join(args.output, str(level)))
        tiles = int(math.ceil(float(size) / args.tileSize))
        if (level < levels):
            face = face.resize([size, size], Image.ANTIALIAS)
        for i in range(0, tiles):
            for j in range(0, tiles):
                left = j * args.tileSize
                upper = i * args.tileSize
                right = min(j * args.tileSize + args.tileSize, size)
                lower = min(i * args.tileSize + args.tileSize, size)
                tile = face.crop([left, upper, right, lower])
                tile.load()
                tile = tile.convert("RGB")
                tile.save(os.path.join(args.output, str(level), faceLetters[f] + str(i) + '_' + str(j) + extension), quality = args.quality)
        size = int(size / 2)

# Generate fallback tiles
print('Generating fallback tiles...')
for f in range(0, 6):
    if not os.path.exists(os.path.join(args.output, 'fallback')):
        os.makedirs(os.path.join(args.output, 'fallback'))
    face = Image.open(os.path.join(inputDir, faces[f]))
    face = face.resize([1024, 1024], Image.ANTIALIAS)
    face = face.convert("RGB")
    face.save(os.path.join(args.output, 'fallback', faceLetters[f] + extension), quality = args.quality)

# Clean up temporary files
if removeFaces:
    for face in faces:
        os.remove(os.path.join(args.output, face))

# Generate config file
text = []
text.append('{')
text.append('    "type": "multires",')
text.append('    ')
text.append('    "multiRes": {')
text.append('        "path": "/%l/%s%y_%x",')
text.append('        "fallbackPath": "/fallback/%s",')
text.append('        "extension": "' + extension[1:] + '",')
text.append('        "tileResolution": ' + str(args.tileSize) + ',')
text.append('        "maxLevel": ' + str(levels) + ',')
text.append('        "cubeResolution": ' + str(cubeSize))
text.append('    }')
text.append('}')
text = '\n'.join(text)
with open(os.path.join(args.output, 'config.json'), 'w') as f:
    f.write(text)