Représentation d'images (Clojure et Java), première partie.

Posted by fmn on septembre 25, 2009 at 12:22 .

Dans un billet précédent (Image numérique et tableau : est-ce identique ?), je me plaignais de l'impossibilité chronique de représenter des images avec des indices négatifs. Pour ne pas rester sur une simple constatation, et aussi parce que j'en ai besoin, je vous livre mes premières lignes de code.

Tout d'abord, quelles sont les contraintes? J'écris mes programmes en Clojure. Pourquoi? Parce que c'est un Lisp et j'aime le Lisp. Ensuite parce c'est un langage rapide, dynamique et tournant sur une machine virtuelle java. Ce dernier point est important car mes programmes doivent être portables. Pour être vraiment portable, ma représentation d'une image devra être complètement transparente pour un développeur Java. Je doit donc écrire une classe. Mais comme je n'aime pas le langage Java, cette classe sera écrite en Clojure. Cela constituera un bon exercice.

Définitions

En généralisant à l'extrême, une image n'est rien d'autre qu'une fonction à deux dimensions et bornée. La classe Imfun devra donc hériter de clojure.lang.IFn, l'interface représente les fonctions en Clojure. Ce classe comporte une méthode invoke qui est appelée lorsqu'une instance est utilisée comme une fonction. Il suffit donc d'avoir comme état interne de chaque instance : un domaine de définition et une fonction. Voici le début de la classe Imfun :

(ns tip.Imfun
  (:gen-class
   :implements [clojure.lang.IFn]
   :state state
   :init init
   :methods [[fun [] clojure.lang.IFn]
             [domain [] clojure.lang.IPersistentCollection]]
   :constructors {[clojure.lang.IPersistentCollection clojure.lang.IFn] []}))
Quelques explications :

  • la première ligne indique l'espace de nom utilisé : tip.Imfun,
  • :gen-class permet la génération des fichiers *.class,
  • la classe implémente l'interface clojure.lang.IFn,
  • l'état interne de chaque instance sera stocké dans state,
  • l'initialisation de chaque instance sera réalisée par la fonction init,
  • deux méthodes seront exposées : fun et domain,
  • les arguments du constructeur de la classe seront : une collection (pour le domaine de définition) et une fonction.

La fonction init doit renvoyer un vecteur comportant : 1/ les arguments à passer au constructeur de la classe parente (rien ici) et 2/ l'état de l'instance crée. Les arguments de init doivent être conformes à ceux définis par :constructors.

(defn -init [dom f]
  [[] [dom f]])
Les deux méthodes fun et domain ont pour rôle de renvoyer la fonction sous-jacente et le domain de définition de l'image, tout deux stockés dans state.
(defn -domain [this]
  (first (.state this)))
(defn -fun [this]
  (second (.state this)))
La méthode invoke est utilisée lors de l'application de l'instance en tant que fonction. Lors de cette application, l'appartenance des coordonnées au domaine de définition doit être vérifiées (via la fonction indom?). Si les coordonnées sont en dehors du domaine de définition, l'application renvoie la valeur 0 (cela sera modifié plus tard).
(defn indom? [dom pt]
  (let [[start end] dom
        [x y] pt]
    (and (>= x (first start))
         (<= x (first end))
         (>= y (second start))
         (<= y (second end)))))

(defn -invoke ([this x y] (if (indom? (.domain this) [x y]) ((.fun this) x y) 0)) ([this pt] (let [[x y] pt] (this x y))))

Organisation des fichiers et compilation

Admettons que mon répertoire de travail soit work. Il faut créer deux sous-répertoire src et classes. Ce dernier contiendra les *.class générés. L'arborescence dans src doît être conforme à l'espace de nom utilsée. Ainsi le code précédent doit être contenu dans le fichier work/src/tip/Imfun.clj. A partir de là, la compilation est réalisée par la ligne suivante :

fredm:work> java -cp clojure.jar:./src:./classes clojure.main -e "(compile 'tip.Imfun)"
(cette compilation pourrait être réalisée dans la boucle d'évaluation de Clojure).

Exemple d'utilisation

Ouvrons la boucle d'évaluation Clojure :

fredm:work> java -cp clojure.jar:./src:./classes clojure.main -e
Clojure 1.1.0-alpha-SNAPSHOT
user=> (import '(tip Imfun))
tip.Imfun
Créons une image, définie de (-1, -1) à (1, 1) et dont la valeur renvoyée vaut la somme des coordonnées :
fredm:work> java -cp clojure.jar:./src:./classes clojure.main -e
Clojure 1.1.0-alpha-SNAPSHOT
user=> (def f #(+ %1 %2))

'user/f

user=> (def im (Imfun. [[-1 -1] [1 1]] f))

'user/im

im est maintenant une fonction bornée, qui renvoie 0 en dehors de son domaine de définition.

user=> (.domain im)
[[-1 -1] [1 1]]
user=> (im -1 -1)
-2
user=> (im 0 1)
1
user=> (im 1 2)
0
Il est possible de passer comme argument à im, soit deux coordonnées x et y, soit une collection de deux éléments comportant les coordonnées (i.e. la représentation d'un point):
user=> (im 1 1)
2
user=> (im [1 1])
2
user=> (im '(1 1))
2
Voila, la prochaine fois nous hériterons de cette classe pour définir des images dont les valeurs seront stockées dans un tableau à deux dimensions. En fait nous utliiserons un ImageProcessor de la librairie d'ImageJ.

FMN.

3 Comments

Trackbacks / Pingbacks

Leave a Reply