Pourquoi Comment Combien le blog du Dr. Goulu
le blog du Dr. Goulu

Couleurs, Gamuts, Python et Open Source

Ce fut une excellente journée de travail, stimulante et productive. Tôt le matin, Cédric m’a montré les slides d’un article sur la mesure géométrique de la différence entre deux gamuts, en me demandant s’il était facile de programmer la méthode présentée [1]

Avant d’attaquer la question et la réponse,  une petite introduction sur le merveilleux monde des couleurs s’impose.

Couleurs et gamuts

En imprimerie, on mesure à l’aide d’un spectrophotomètre l’ensemble des couleurs produites par une imprimante, ne serait-ce que pour la calibrer [2]. Mais lorsqu’on développe une imprimante industrielle, il faut en plus mesurer l’effet sur les couleurs de nombreux paramètres (qualité des substrats, composition des encres, puissance des séchoirs etc. ) afin de maximiser le « volume » des couleurs que l’imprimante est capable de reproduire.

cube des couleurs RGB (Red Green Blue)
cube des couleurs RGB (Red Green Blue)

L’ensemble des couleurs définit un volume car il faut trois paramètres pour déterminer une couleur. La représentation la plus connue est celle basée sur les couleurs primitives rouge, vert et bleu, le « RGB ». Sur votre écran, des pixels rouges, verts et bleus peuvent être allumés avec des intensités variables, habituellement codée par un entier entre 0 et 255. La synthèse additive permet ainsi de vous faire percevoir 16’777’216 couleurs différentes définies par autant de points dans le cube ci-contre.

De même, on peut faire un cube « CMY » avec les couleurs produites par synthèse soustractive en déposant des encres cyan, magenta et jaune sur du papier supposé blanc.

Oui mais voilà, comme vous vous en apercevez chaque fois que vous imprimez des photos à la maison, on n’obtient pas les mêmes couleurs en RGB et en CMY. Les deux cubes ne se superposent pas bien. Intuitivement, on comprend qu’un écran ne peut pas être plus noir que quand il est éteint, et qu’on ne peut pas rendre le papier plus blanc qu’il ne l’est originellement. Le même raisonnement s’applique aux autres directions de l’espace des couleurs : certaines couleurs possibles en RGB ne peuvent pas être obtenues en impression CMY, et vice-versa. C’est d’ailleurs pour ça que les imprimantes photo ont des couleurs supplémentaires, souvent du « magenta clair » et du « cyan clair » pour mieux imprimer vos couchers de soleils. Et aussi du noir, mais c’est plutôt pour imprimer du plus joli texte et économiser de l’encre des 3 autres couleurs.

Les couleurs que l’on peut reproduire fidèlement se trouvent dans l’intersection des deux cubes, mais les axes des deux cubes ne sont pas les mêmes, alors comment faire ?

En les représentant dans un autre espace de couleurs, plus vaste, le CIE_L*a*b*. Dans cet espace, la coordonnée L* correspond à la luminance et les coordonnées a* et b* à des échelles entre couleurs tenant compte de la sensibilité de l’oeil humain. En LAB, le cube CMY d’une imprimante donnée devient un patatoïde comme celui représenté ci-dessous, et le cube RGB d’un écran précis devient le volume enfermé dans le treillis. Comme on le voit, l’intersection des deux est un volume compliqué, nettement plus petit que chacun des patatoïdes.

Gamut d
Gamut d’une imprimante (solide) et d’un moniteur (treillis). Image : Michael J. Vrhel

Retour au Code

Bon mais alors, est-ce qu’on peut faire facilement un programme qui calcule l’intersection de deux gamuts quelconques ? Dans ces cas là la réponse standard est « ça dépend » :

  • si le code Matlab décrit dans l’article [1] est disponible, c’est tout cuit. Et peut-être même gratuit si  Octave suffit.
  • sinon (je n’ai pas trouvé le code…), ça dépend de l’existence de librairies (Python de préférence) effectuant les opérations délicates:
    • Lire les fichiers produits par le  : pas de problème car ce sont des .txt avec les valeurs LAB des quelques dizaines de couleurs mesurées
    • Obtenir l'enveloppe convexe* des points : pas de problème non plus. J’avais déjà utilisé la fonction scipy.spatial.ConvexHull basée sur la librairie QHull [3]. C’est d’ailleurs la même qu’utilise la fonction Matlab convhulln. Et elle fournit même le volume du polyèdre en prime!
    • Par contre, je n’ai aucune idée de comment calculer l’intersection des deux polyèdres / gamuts. Ou plutôt si : je sais que si je ne trouve pas du code faisant ça vite et bien, je vais passer longtemps à le faire mal. En gros il faut calculer l’intersection de la surface en fil de fer avec la surface colorée dans l’image ci-contre, et vice-versa. L’algorithme le plus simple qui vient à l’esprit est de vérifier si chaque côté des petits triangles formant une surface intersecte un (ou deux …) triangles formant l’autre surface, puis de refaire une enveloppe convexe de tous points d’intersection. Comme tout algo simple, il y en a probablement un bien plus efficace quelque part …

En cherchant un peu, je tombe sur la solution : trimesh, un package Python capable de faire des « Boolean operations on meshes (intersection, union, difference) ». Je l’installe, je code le gros de l’application, et exactement une heure après avoir attaqué le problème, je vois le volume de mes gamuts s’afficher ! mais l’appel à la fonction calculant l’intersection crashe…

C’est la saison des châtaignes : débogage

  • d’abord il faut installer RTree, un wrapper Python de libspatialindex. J’avais déjà rencontré cette librairie permettant de trouver rapidement les points les plus proches d’un autre en utilisant la structure d'arbre R* [5] mais n’étais pas sur d’en avoir vraiment besoin. Un message d’erreur me dit que si… Sur une machine Windows, cette librairie ne s’installe pas « toute seule », il faut aller la chercher sur la fabuleuse page « Unofficial Windows Binaries for Python Extension Packages« 
  • ensuite je découvre pourquoi la phrase « Boolean operations on meshes (intersection, union, difference) » avait une suite : « if OpenSCAD or Blender is installed ». En fait, trimesh appelle l’un ou l’autre de ces logiciels pour faire le vrai boulot. Connaissant l’existence des deux, mais ayant joué avec Blender il y a quelques années et me souvenant qu’il utilise Python comme langage de script, j’opte pour Blender et …
  • … le code tourne ! Mais me renvoie toujours un volume de l’intersection de mes gamuts nul. Ce sont les fichiers STL temporaires que trimesh crée qui sont encore ouverts lorsque Blender y accède. Bizarre, parce que trimesh uilise travis-ci pour effectuer des tests automatiques et que ceux-ci ne montrent aucun problème … Mais les tests tournent sur une machine (virtuelle) Linux ! Apparemment, un pingouin a le droit de lire un fichier qu’un autre pingouin a laissé ouvert, mais une fenêtre doit être refermée avant qu’elle puisse être réouverte… Je vais devoir modifier le code de trimesh pour corriger ça, et je décide de le faire bien.

L’Open Source, c’est bon pour le business

%image_alt%En effet, je ne sais pas si vous avez tout suivi mais je travaille dans une entreprise qui vend des machines pour gagner du pognon et payer quelques salaires en passant. Or tous les logiciels utilisés pour développer cette application relativement pointue en une seule journée sont « Open Source », gratuits. La moindre des choses est, me semble-t-il, de renvoyer parfois l’ascenseur partageant les améliorations que j’apporte à ces logiciels, évidemment dans la mesure où ça ne va pas à l’encontre des intérêts de mon employeur.

J’ai donc forké le source de trimesh disponible sur GitHub, modifié le fichier qui gère les fichiers temporaires et hop ! l’application fonctionne après grosso-modo 4 heures de boulot et 4 cafés. Et comme les tests de trimesh passent toujours (sur Linux), je soumets une « pull request » à l’auteur de trimesh pour lui proposer d’incorporer mes changements à la branche officielle : B.A. accomplie en une heure supplémentaire à tout casser.

Après ça il me restait encore un moment pour implanter une idée qui m’avait traversé l’esprit : compter combien de couleurs Pantone sont incluses dans chaque gamut (et donc possible d’imprimer avec notre machine), et lister celles qui n’y sont pas. Mais comme les valeurs Lab de ces couleurs sont protégées par copyright, je ne pourrai pas mettre ce code à disposition en Open Source. Des centaines de milliers de lignes de code testé et performant oui, mais une table de couleurs, non…

Note* : ce qu’il y a de bien en écrivant un article, c’est que ça met les idées en place, ou que ça instille un léger doute. En regardant les polyèdres des gamuts RGB et CMY dans LAB, on voit qu’ils ne sont pas convexes… Pour être plus précis, peut-être bien qu’il me faudrait utiliser un algo plus subtil que ConvexHull …

Références:

  1. Deshpande, K., Green, P., & Pointer, M. R. « Metrics for comparing and analyzing two colour gamuts », 2015, Color Research & Application, 40(5), 465–471. DOI : 10.1002/col.21930 (slides pdf)
  2. Arnaud Frich « Guide de la Gestion des Couleurs – le calibrage de l’imprimante« , 2016
  3. Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., « The Quickhull algorithm for convex hulls, » ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org
  4. Vrhel, M. J., & Trussell, H. J. « Color Device Calibration: A Mathematical Formulation« , 1999, IEEE Transactions on Image processing, 8(12).
  5. Beckmann, N., & al. « The R*-tree: an efficient and robust access method for points and rectangles » 1990, In Proceedings of the 1990 ACM SIGMOD international conference on Management of data  – SIGMOD ’90 (Vol. 19, pp. 322–331). New York, USA: ACM Press. DOI : 10.1145/93597.98741

Laissez un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

3 commentaires sur “Couleurs, Gamuts, Python et Open Source”