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-classpermet 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 :
funetdomain, - 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.ImfunCré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) 0Il 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)) 2Voila, 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.
- Une sélection (automatique) de billets similaires :
- Plugin ImageJ minimal en Clojure: inverser une image
- Représentation d'images (Clojure et Java) #2
- Utiliser ImageJ dans un notebook Sage, un exemple d'appel de Java depuis Python
- Image numérique et tableau : est-ce identique ?
- Fonction avec méta-données (Clojure). Représentation d'images #3
[...] fois j’avais montré comment représenter une image en tant qu’une fonction bornée (voir ce billet). La classe java était intégralement écrite en Clojure. Cette représentation correspond à la [...]