Programmation ImageJ

Transcription

Programmation ImageJ
Programmation ImageJ
1.Intérêt de la programmation orientée objet
Avant-propos
Avant de commencer à programmer dans ImageJ, il faut mieux avant bien comprendre ce qu'est la
Programmation Orientée Objet, son vocabulaire et ce qui la différencie de la programmation
linéaire (appelée aussi procédurale). Sinon, il vous sera impossible d'écrire un plugin ImageJ, voire
un programme Java. 1.1 Principe de la POO : le choix de Java
La programmation objet s'appuie sur l'utilisation d'objets (non sans rire ...), c'est­à­dire que vous
créez un objet qui va avoir une fonction précise. L'intérêt de cet objet est qu'il est réutilisable dans
n'importe quel programme. Prenons un exemple simple, créons un objet, appelons le connexion, dont le but est de vous
connecter à une base de données. Vous codez une fois cet objet et ensuite, dans tous vos
programmes, il vous suffit de l'appeler. Plus besoin de re­coder cette portion de code. Après le
codage de plusieurs objets, on peut dire que la conception d'un programme ressemble plus à un jeu
de Lego. Vous y incorporez les objets dont les fonctions vous sont utiles. Je pense que vous voyez
l'intérêt ... Vous pouvez récupérer des objets et les réutiliser à volonté donc gain de temps. De plus, Java
possède une très grande bibliothèque d'objets prêts à servir. 1.2 POO vs. Programmation linéaire
Lors d'une programmation linéaire (ou procédurale), on commence avant tout par chercher les
taches à accomplir, puis on code des procédures qui, étape par étape, vont résoudre ces taches.
Donc, on travaille avant tout sur les méthodes. En POO, on commence d'abord par isoler les classes
(les actions si vous voulez) et seulement après on s'occupe des méthodes. De plus, en POO, chaque
méthode est associée à une classe. Que nous apporte cette différence ? D'abord, les classes regroupent les méthodes créant ainsi une
bibliothèque de fonctionnalités plus facilement utilisables que des bouts de code. De plus,
l'encapsulation évite la corruption par d'autres objets. Donc, si vous avez un bug, cela ne peut venir
que de votre classe et non pas d'une autre. Il en découle qu'au lieu d'examiner les 20000 lignes de
code, vous n'avez qu'à regarder celles qui sont en rapport avec la classe défaillante.
Bon, je vous vois venir, certains vont dire qu'en php quand ils codent, ils font des fonctions qu'ils
appellent via des include. En fait, là, vous créez des modules. Mais ces modules ne peuvent avoir
qu'une seule instance. Si vous voulez une autre instance, vous devez faire une copie de ce module et
faire quelques modifications. Or en POO, une classe peut avoir un nombre illimité d'instances
contrairement à vos modules. 1.3.Bases de Java
Nous voilà arrivés dans le vif du sujet. Nous allons nous familiariser avec la structure d'un
programme Java. En effet, les éléments qui le composent se retrouvent dans toutes les applications.
1.3.1 le code de notre programme public class PremierScript
{
public static void main(String [ ] args)
{
System.out.println("Mon premier script, c'est facile finalement");
}
}
Tout d'abord je tiens à vous rappeler une chose : Java est sensible à la casse. J'explique pour ceux
qui ne connaissent pas ce terme. Lorsque vous tapez votre code il faut respecter les majuscules et
minuscules. Par exemple si à la place de : public class PremierScript
Vous tapez : Public class PremierScript
Java ne reconnaîtra pas le mot clé public. Et oui tout cela à cause de la lettre « p » en majuscule. Il
vous faut donc être rigoureux là­dessus dès le départ. public Ce mot clé est appelé un modificateur d'accès, c'est­à­dire qu'il détermine quelles autres
parties du programme ont accès à cette classe. class Tout ce que l'on programme se situe dans une classe, le terme ouvre donc une classe. PremierScript le terme se trouvant derrière le mot clé class est le nom de la classe. Vous pouvez donner
n'importe quel nom à une classe sauf des noms de fonctions. Une règle pourtant, un nom de
classe commence toujours par une lettre. Une autre remarque s'impose. Il faut toujours donner
le même nom à votre fichier source que celui de la classe publique de votre programme. Ici
notre code sera donc sauvegardé sous le nom de PremierScript (respecter là aussi la casse). main Le mot clé main ouvre une méthode. Lorsque vous exécutez un programme, Java va toujours
chercher la méthode main. Sans cette méthode l'exécution est impossible. Bien sûr vous
pouvez coder d'autres méthodes que la main mais cette dernière doit toujours être présente
dans vos scripts. Notez bien les accolades. En java, comme dans d'autres langages, chaque bloc est délimité par une
accolade. Donc pour résumer tous vos scripts doivent au moins avoir ça : public class NomdelaClasse
{
public static void main(String [ ] args)
{
Les instructions
}
}
Car la classe publique est celle qui est appelée lors de l'exécution du programme et la méthode main
est la méthode par défaut donc celle exécutée. En effet si on réfléchit lors de l'exécution de votre
script vous tapez : Java NondelaClasse
Vous appelez donc la classe Nomdelaclasse, or là vous ne pouvez spécifier quelle méthode vous
appelez. Il faut donc que java ait une méthode à lancer par défaut. Devinez laquelle ? Oui, la main.
Maintenant regardons ce que contient la méthode main de notre programme. Ici nous avons qu'une seule instruction : System.out.println("Mon premier script, c'est facile finalement");
Première remarque : Comme pour les macros, chaque instruction doit se terminer par un ; donc les retours à la
ligne ne délimitent pas la fin d'une instruction. Ce qui d'un autre coté peut être plus que
sympathique. Imaginez que vous avez une instruction très complexe et longue. Pour plus de
lisibilité vous pourrez étaler son code sur plusieurs lignes. Ici notre instruction affiche une
ligne de texte dans la console en l'occurrence : Mon premier script, C'est facile finalement
Nous utilisons l'objet System.out et nous appelons sa méthode println. Pour invoquer une méthode d'un objet on utilise donc le . Objet . méthode(paramètre)
La méthode println affiche le texte passé en paramètre puis saute une ligne. Histoire que vous le
sachiez la différence, si vous aviez appelé la méthode print au lieu de println vous n'auriez pas eu de
saut de ligne après l'affichage de votre texte. Je récapitule, nous avons donc vu la structure de base
d'un programme java. Sans cela votre script ne peut s'exécuter. Et nous avons aussi abordé comment
nous appelions une méthode d'un objet. Voyons maintenant les commentaires. 1.3.2. Les commentaires Les commentaires sont essentiels dans un script. Abusez­en, cela ne rendra votre travail que plus
facile et comblera de bonheur les personnes qui vous reliront ou qui voudront se baser sur votre
plugin pour en écrire un autre. Java ne compile pas les commentaires , ils n'apparaissent donc pas
dans votre exécutable. Pour mettre des commentaires on a le choix entre // et /* */ . Le // sert pour une ligne de
commentairealors que /* */ englobe un bloc de commentaire. /*
Code source de PremierScript
Ici on n'est pas dans ImageJ, pas besoin de « _ » dans le nom de la classe
*/
public class PremierScript
{
public static void main(String [ ] args)
{
//Affichage du texte dans la console
System.out.println("Mon premier script,c est facile finalement");
}
}
Vous avez maintenant les bases pour commencer à coder en java. Au cours des quelques chapitres
suivants, je vais rappeler un certain nombre de notions générales qu'il faudra assimiler avant
d'espérer programmer un plugin ImagJ. Vous pourriez aussi le faire « à l'instinct » en regardant
beaucoup de codes sources de divers plugins, c'est d 'ailleurs comme ça que la plupart d'entre nous
fonctionnent, mais vous n'aurez pas la pleine compréhension du fonctionnement et surtout des
causes de non­fonctionnement d'un plugin (cela arrivera forcément).
1.3.3. Les variables Après avoir vu les notions de programmation orientée objet et la structure d'un programme de base,
nous allons maintenant aborder le vaste sujet que représentent les variables. Quels noms leur
attribuer, quels types sont à votre disposition, l'affectation, les différentes opérations réalisables ...
Cette étape est essentielle dans l'apprentissage de Java. Une variable est un contenant où vous allez pouvoir mettre ce que vous voulez (les données). Lors
de l'exécution de votre programme vous pourrez agir sur ces variables pour en modifier leurs
valeurs (les données) via de multiples opérations. Voilà pour le concept de variable. Maintenant
voyons quelques règles de base concernant la nomination de ces dernières. ●
●
●
●
Le nom de votre variable devra toujours commencer par une lettre. Les espaces ne sont pas autorisés mais vous pouvez utilisez le _en remplacement Votre nom peut comporter des lettres et des chiffres Vous ne pouvez pas attribuer comme nom de variable le nom d'une fonction java du style if,
inner ou autre. L'un des grands avantages de Java est que l'allocation et la libération de la mémoire (valeur en byte)
sont automatiques. Mais cela ne vous dispense pas de devoir déclarer vos variables et surtout le type
de la variable. Nous avons 8 types de variables : ●
●
●
●
4 pour travailler sur les nombres entiers (byte, short, int et long)
2 pour travailler sur les nombres réels (double et float)
1 pour travailler sur les chaînes de caractères (char)
le type booléen (boolean)
Déclarations des variables
La déclaration d'une variable est quelque chose de vraiment facile. Rien de bien compliqué, on
spécifie d'abord le type de la variable puis son nom. Chaque déclaration étant une instruction on
termine par un « ; » . D'habitude, on déclare toutes les variables au début du bloc où on les utilise. Exemple : int nom_variable ;
Affectations de valeur
Après la déclaration d'un variable, il est temps de la remplir, c'est­à­dire de lui affecter une valeur.
On appelle aussi cette procédure l'initialisation. Pas besoin de longs discours un exemple sera suffisant : int nom_variable ;
nom_variable =1 ;
Mais bon comme les choses sont quand même bien faites en ce bas monde de temps en temps il est
possible de déclarer et d'initialiser une variable en même temps : int nom_variable =1 ;
Les constantes
Il existe une forme particulière de variable, les constantes. Les constantes sont des variables dont la
valeur ne pourra être modifiée dans le programme, des variables constantes quoi ... je sais c'est
antinomique ...
Pour définir une constante on utilise le mot­clé final. Final int NOM_VARIABLE =1 ;
À partir de ce moment NOM_VARIABLE aura toujours la valeur 1 dans le bloc où elle a été
déclarée. Beaucoup de programmeurs écrivent en majuscule le nom de leur variable, histoire d'améliorer la
visibilité du code. Bon maintenant si nous voulons une constante accessible par toutes les méthodes
d'une classe, il faut définir ce que l'on appelle une constante de classe. On définit alors la constante
de classe en dehors de la méthode main permettant ainsi à d'autres méthodes de pouvoir l'appeler.
Le mot­clé employé est ici : static final Un petit exemple : public class Tuto_variable
{
public static final int NOM_VARIABLE =2;
public static void main(String [] arg)
{
int a =2;
int b;
b =a *NOM_VARIABLE;
System.out.println("le Résultat est :"+b);
}
}
Ce petit programme va tout simplement afficher le chiffre 4. Rien d'extraordinaire je le reconnais
mais nous avons bien la preuve que la constante de classe est accessible. Je sais vous allez me dire : Pourquoi avoir mis public static final ? Il y a ici une petite notion en plus. Avec static final la
constante de classe est uniquement accessible à l'intérieur de la classe où elle est définie. Pour la
rendre accessible par d'autres classes nous plaçons le mot clé public. Pour appeler la constante d'une
autre classe nous taperons : Tuto_variable.NOM_VARIABLE et le tour est joué. 1.3.4.Les opérateurs Pour ceux qui ont déjà l'habitude de la programmation, ce chapitre sur les opérateurs ne vous sera
pas d'une grande utilité. Mais il faut bien penser à tout ceux qui débutent. Opérateur mathématique
Opération
Signification +
Addition
­
Soustraction
*
Multiplication
/
Division
%
Reste ou modulo
Il existe plusieurs façons d'écrire les opérateurs : x=x1 Équivalent : x+=1 Opérateurs d'incrémentation et de décrémentation
●
●
[++]incrémentation. Ajoute 1 [­]décrémentation. Enlève 1 int n =1 ;
n++;//n vaut 2 maintenant
Opérateurs relationnels et booléens
●
[==]Égale ou équivaut Attention, cela ne marche que pour le numérique. Pour les chaînes de caractère la fonction est : chaine1.equals(chaine2) ●
●
●
●
●
●
●
[!=]différent [>]supérieur [<]inférieur [>=]supérieur ou égal [<=]inférieur ou égal [&&]et [||]ou 1 == 2 ;//C'est faux 1 != 2 ;//ça c'est vrai Voici donc les différents opérateurs principaux utilisables en java. 1.3.5. Conversion de variables Il vous arrivera bien souvent de devoir convertir une variable dans un autre type pour pouvoir
travailler dessus. Exemple : int a =233333 ;
float b =a ;//b sera une variable de type float avec la valeur de a
Pour convertir une chaîne de caractère en chiffre : String a=«40 » ;
Int b =Integer.parseInt(a);
1.3.6. Boucles et Conditions Avec cette section, nous allons enfin posséder les bases de la programmation. Vous allez pouvoir
élaborer des algorithmes de programmation utilisant des boucles, des conditions et bien sûr des
boucles conditionnelles. Nous allons donc pouvoir par la suite créer de petits programmes simples.
Par la suite, il vous faudra acquérir un nouveau type de connaissances, ce que j'appelle les
« fonctions utiles de Java » , c'est­à­dire celles qui vous permettent par exemple d'utiliser la
bibliothèque d'ImageJ. Les conditions
Il arrive bien souvent que nous ayons à tester une variable ou exécution d'une fonction. Pour ceux
qui ont déjà programmé ces notions devraient être acquises. Exemple : nous sommes dans une section membres où l'utilisateur doit s'identifier pour accéder à la
section. Si l'utilisateur est identifié, on le redirige vers la page de son compte ; sinon, on
affiche un message l'invitant à se retenter son identification. Pour cela, on a utilisé le code : if(la_condition)
{bloc de code pour la condition juste}
else
{bloc de code pour la condition fausse}
En gros voila ce que cela donne : import javax.swing.*;
public class Tuto_if_else
{
public static void main(String [] arg)
{
String a ="fliper";
String demande =JOptionPane.showInputDialog("Votre identifiant :");
if(demande.equals(a))
{
System.out.println("Vous etes identifié ");
}
else
{
System.out.println("Erreur,vous n\'etes pas identifié ");
}
System.exit(0);
}
}
Il y a dans cet exemple deux nouvelles notions: ●
●
la comparaison entre deux chaînes de caractères une boîte de dialogue pour entrer la valeur de la variable demandée la boîte de dialogue
String demande =JOptionPane.showInputDialog("Votre identifiant :");
Cette commande permet d'ouvrir une boîte de dialogue contenant le message mis entre parenthèses.
La méthode showInputDialog de la classe JOptionPane renvoit une chaîne de caractères donc si
vous demandez un nombre vous devrez convertir la variable de cette manière : int demande =Integer.parseInt(demande);
Ensuite notez le : System.exit(0);
Chaque fois que vous appelez JOptionPane.showInputDialog vous devez terminez votre
programme par System.exit(0);. La raison est purement technique. En fait l'affichage d'une boîte de
dialogue démarre un nouveau thread de contrôle. Lors de la sortie de votre méthode main, le
nouveau thread ne se termine pas automatiquement. Il faut donc tous les clore grâce à cette
commande. Maintenant terminons par : import javax.swing.*;
En fait, la classe JOptionPane est définie dans le package jawax.swing et il ne fait pas partir
du package de base (java.lang). Il vous faut donc l'importer grâce à la commande import. Comparaison de deux chaînes de caractères
L'égalité entre deux nombres a et b se teste comme ceci : a == b . Mais pour deux chaînes de
caractères a et b, on fait : a.equals(b). Souvenez­vous bien de cette différence, ainsi que du fait que
JOptionPane.showInputDialog renvoie toujours une chaîne de caractères. Astuce : Mettez de la tabulation pour chaque bloc de code, les éditeurs de texte bien fait (comme vi ...)
identent tout seul le code. Cela vous permettra de le rendre bien plus lisible, et si vous avez un
bug vous passerez moins de temps à chercher l'accolade (par exemple) que vous n'avez pas
fermée. Il arrive aussi qu'en faisant un algorithme conditionnel (terme barbare qui veut juste
dire « si condition alors ») on se retrouve avec plusieurs cas possibles. Exemple : Si j'ai de l'argent, soit je pars en vacances dans le sud pour 100 euros, soit si j'ai plus que cela
je pars aux Caraïbes (450 euros) et si j'ai rien ... je reste chez moi. En code ça donne : if(argent==100)
{je pars dans le sud}
elseif(argent==450)
{Finalement j'ai assez pour partir aux Caraïbes}
else
{Bon je mange des pâtes, je n'ai pas assez d'argent}
Donc: if(premier_condition)
{Block de code si première condition vraie}
if(deuxième condition car première fausse)
{Block de code deuxième condition vraie}
else
{Block de code traitant le cas où les deux conditions sont fausses}
Il est bien évident que l'on peut faire autant de elseif que l'on souhaite. Petit exercice :
Reprenez le code de l'identification : Tuto_if_else et renommez le Tuto_if_elseif .Vous
déclarez une nouvelle variable char et vous lui attribuez un nom d'utilisateur puis vous me
rajoutez un elseif permettant de tester l'entrée de l'utilisateur. Le programme va alors vérifier
si l'utilisateur est fliper ou l'autre membre, si oui c'est bon, sinon c'est faux ! Malheureusement, vous vous doutez bien que le code va vite devenir illisible si l'on a énormément
de possibilité dans l'algorithme conditionnel. Pas de panique, on a pensé à tout. Dans ce cas précis,
il existe la fonction switch !!! switch(variable_a_tester)
{
case 1 :
bloc de code
break;
case 2 :
bloc de code
break;
}
default :
Bloc de code si aucun case n'est vrai
break ;
}
Dans le switch, on définit la variable qui va se faire tester, puis au niveau des case on met la valeur
test. Si la condition est remplie, le bloc de code de ce case est utilisé, sinon, on passe à l'autre case.
Si aucun case n'est vrai et que vous avez un default, ce dernier sera exécuté. Remarquez que chaque
case se termine par break. Cette commande permet de délimiter le bloc de code du case alors ne
l'oubliez pas. Autre remarque très importante la valeur des case doit TOUJOURS être un entier . Conclusion :
Nous venons de voir les fonctions qui permettent de faire des tests. Vous vous
apercevrez très vite que vous allez les utiliser à longueur de journée donc mémorisez­
les bien. Les boucles
Boucle for
Souvenez­vous de vos punitions du primaire : copier 100 fois je ne ferai plus l'andouille près du
radiateur ... souvenir, souvenir... Et on passait la soirée à écrire 100 fois la même phrase !!! En java, écrire 100 fois cette phrase
punitive nous prendrait 5s. Pour cela, nous allons faire une boucle. Je savais bien que j'aurais dû
apprendre le java en maternelle ça m'aurait bien servi ! La boucle est une fonction qui va exécuter un nombre de fois défini le code que l'on souhaite. for(init_compteur;valeur_de_fin;incrementation_du_compteur)
{
bloc de code à répéter
}
donc pour notre punition cela donne : public class Punition
{
public static void main(String [] arg)
{
int a ;
for(a=0 ;a<100 ;a++)
{
System.out.println("je ferai plus l'andouille près du radiateur");
}
}
}
Vous allez voir s'afficher 100 fois la phrase sur votre écran de sortie. Le danger de la boucle infinie
Avertissement : faites attention à vos boucles. Une boucle mal faite peut devenir une boucle infinie
c'est­à­dire qui ne s'arrête jamais. Les conséquences peuvent être plus que fâcheuses
car vous allez saturez la mémoire de votre machine et donc sûrement la faire planter.
Avec le for, il y a peu de chance d'être dans ce cas mais avec la prochaine fonction que
nous allons voir, il vaut mieux être prudent !!! Boucle conditionnelles
C'est pas le terme exact mais à mon avis il résume bien leurs fonctions. En gros la fonction while
veut dire : tant que la condition est vraie alors tu continues à exécuter la boucle . Par exemple, on a
une variable int nommée a et on lui donne la valeur de 0 while(a<25)
{
System.out.println(a);
a++;
}
A chaque passage de la boucle, on a la variable a qui s' incrémente. Lorsqu'elle aura attend la valeur
de 25, on sortira de la boucle. Revenons au danger de tout à l'heure, c'est­à­dire de la boucle infinie.
Imaginons que l'on n'incrémente plus la variable a. while(a<25)
{
System.out.println(a);
}
Puisque la variable a reste toujours inférieure à 25, la boucle va tourner infiniment. Il existe aussi une autre écriture pour while, cette dernière exécute d'abord le bloc puis effectue le
test pour savoir si oui ou non on rentre en boucle. Pour cela on écrit : do
{
System.out.println(a);
a++;
}
while(a<10)
Voila nous avons fait le tour des boucles et des conditions. On peut dire que vous avez à partir de
maintenant de bonnes bases pour attaquer réellement la programmation en java. Pour eux qui ont
déjà programmé, ces connaissances sont bien assimilées. Pour les débutants, n'hésitez pas à relire et
à poser vos questions. Vous trouverez aussi des aides bien faites sur le Web.
Bon courage à tous. 2.La programmation ImageJ
2.1. Généralités
ImageJ est un logiciel dont les fonctions (dont la plupart sont des plugins elles­mêmes) peuvent être
étendues par le biais de plugins. Entendez en français « modules » mais nous utiliserons le terme
anglais par la suite. Les plugins sont des classes Java (on commence à connaître ...) placées dans un
certain dossier, le dossier plugins d'ImageJ ou un de ses sous­dossiers. 2.1.1 « Quick Way »
On peut créer un plugin de façon très simple, exactement comme une macro avec l'enregistreur
(Plugins > Macros > Record ...). On clique sur « Create » pour obtenir un fichier texte qu'on peut
appeler comme on veut. Si dans ce mini­éditeur de texte, on va vers « Edit > Convert to plugin »,
une nouvelle fenêtre s'ouvre qui porte le nom de votre nommage précédent assorti d'un « _ » avec
l'extension « .java ». Le nom par défaut est « Macro » dans l'enregisteur donc « Macro_.java » pour
notre macro Java. Cette fonctionnalité est très pratique car non seulement elle permet de développer
un plugin très rapidement mais en plus elle vous créée le squelette de base de quasiment tous vos
futurs plugins.
import ij.*;
import ij.process.*;
import ij.gui.*;
import java.awt.*;
import ij.plugin.*;
public class Youpi_ implements PlugIn {
public void run(String arg) {
IJ.run("Lena (47K)");
IJ.run("Add Noise");
IJ.run("Smooth");
}
Voilà un exemple de code d'un tel plugin. On remarque l'import de différents packages, le fait que
la classe principale doit contenir un « _ » dans son nom et qu'elle implémente une certaine classe
PlugIn.
2.1.2.Types de plugins
Il y a deux types de plugins. Ceux qui n'ont pas besoin d'image en entrée et qui implémentent la
classe PlugIn (comme ci­dessus car il ouvre une image­échantillon) et ceux qui en ont besoin et qui
implémentent donc la classe PlugInFilter.
2.2.Petite présentation1 de l'API2
2.2.1. Les classes
La classe principale est ij, de là dérive tout le reste.
ij
ImageJApplet Classe nécessaire pour faire tourner ImageJ dans un navigateur. L'avantage est qu'une
installation n'est pas nécessaire, le désavantage est un accès limité aux ressources de la
machine à cause de l'aspect sécurité des applets Java.
ImageJ
La classe principale d'une application ImageJ. Elle contient la méthode run, principal
point d'entrée du programme, et la fenêtre principale d'ImageJ.
Executer
Une classe pour exécuter les commandes des menus dans des threads séparés (pour ne
pas bloquer le reste du programme).
IJ
Une classe qui contient beaucoup d'utilitaires.
ImagePlus
La représentation d'une image dans ImageJ, basée sur un ImageProcessor.
ImageStack
Une pile d'images de taille variable.
WindowManager
L'ensemble des fenêtres ouvertes.
ij.gui
ProgressBar
Une barre qui informe graphiquement de la progression d'une opération.
GenericDialog
Une boîte de dialogue modulaire pour interagir avec l'utilisateur.
NewImage
Pour créer de nouvelles images en partant de zéro.
Roi
Une classe qui représente une région d'intérêt d'une image. Si le plugin le supporte, on
peut traiter la région d'intérêt seule et non pas toute l'image.
ImageCanvas
1 La plus grande part de ce qui suit est une traduction de l'excellent « Writing ImageJ Plugins » de Werner Bailer
2 Application Program Interface
Un support pour dessiner une image, dérivé de java.awt.Canvas.
ImageWindow
Une fenêtre pour afficher une image, dérivée de java.awt.Frame.
StackWindow
Un ImageWindow pour les stacks.
HistogramWindow
Un ImageWindow pour les histogrammes.
PlotWindow
Un ImageWindow pour les tracés de courbes.
ij.io
Un paquet de gestion des entrées­sorties, à savoir toutes les classes pour lire et écrire les
images.
ij.measure
Les classes relatives aux mesures.
ij.plugin
La plupart des commandes ImageJ sont des plugins et se retrouvent donc dans cette
classe ou ses dérivées.
PlugIn
Cette interface doit être implémentée par tout plugin qui n'a pas besoin d'image en
entrée.
Converter
Implémentation pour convertir correctement des ImagePlus d'un type vers un autre.
ij.plugin.filter
PluginFilter
Cette interface doit être implémentée par tout plugin qui nécessite une image en entrée
(qui effectue donc une opération sur cette image).
ij.process
ImageConverter
Une classe contenant les méthodes pour convertir les images d'un type dans un autre.
ImageProcessor
Une super­classe abstraite pour certains types d'images. Un imageProcessor contient
les méthodes permettant de travailler réellement sur les données contenues dans l'image.
StackConverter
Le pendant de ImageConverter pour les stacks.
StackProcessor
Le pendant de ImageProcessor pour les stacks.
ij.text
Paquet qui contient les classes pour afficher et éditer du texte.
2.2.2 Interfaces
Comme déjà évoqué en 2.1.2, ImageJ peut créer deux types de plugins; ceux qui n'ont pas besoin
d'une image en entrée et qui implémenteront l'interface PlugIn, et ceux qui en ont besoin et qui
implémenteront l'interface PlugInFilter.
PlugIn
En 2.1.1, nous avons déjà vu un plugin qui implémentait l'interface Plugin. Il avait été généré
automatiquement par l'enregistreur de macros et transformé en plugin. Nous ne savions rien de
l'API d'ImageJ. Maintenant, nous pouvons développer nos plugins sans utiliser l'enregistreur.
À tout seigneur, tout honneur ; le premier programme développé par quelque programmeur que ce
soit et dans tous les langages est un programme qui se borne à afficher un simple « Hello, World! ».
Nous ne sacrifierons pas à la tradition :
import ij.*;
import ij.plugin.*;
/**
* Hello_.java
* @author
Moi-même
*/
public class Hello_ implements PlugIn {
public void run(String str) {
IJ.showMessage("Hello World en Java pour ImageJ !");
}
}
Commentaire de code :
Nous importons d'abord les paquets dont nous aurons besoin ( mot­clé : import ), suivent
quelques lignes de commentaires au format javaDoc3. Nous déclarons ensuite notre classe Hello en
n'oubliant pas le nécessaire « _ » dans son nom ; cette classe implémente l'interface PlugIn (d'où
l'import de ij.plugin.* qui contient notre interface). Cette interface n'a qu'une méthode
publique : run. L'import de ij est nécessaire pour la méthode IJ.showMessage.
Note : Nous verrons dans le cadre des travaux pratiques d'autres implémentations de l'interface
PlugIn.
PlugInFilter
import ij.*; //Paquet général
import ij.plugin.filter.PlugInFilter; // L'interface PlugInFilter
3 http://java.sun.com/j2se/javadoc/
import ij.process.*; // Les différents ImageProcessor
import java.awt.*;
public class Inverter_ implements PlugInFilter {
public int setup(String arg, ImagePlus imp) {
if (arg.equals("about")) {
showAbout();
return DONE; }
return DOES_8G+DOES_STACKS+SUPPORTS_MASKING;
}
public void run(ImageProcessor ip) {
byte[] pixels = (byte[])ip.getPixels(); //Notez le «cast»4 en byte ()
int width = ip.getWidth();
Rectangle r = ip.getRoi();
int offset, i;
for (int y=r.y; y<(r.y+r.height); y++) {
offset = y*width;
for (int x=r.x; x<(r.x+r.width); x++) {
i = offset + x; pixels[i] = (byte)(255-pixels[i]);
}
}
}
void showAbout() {
IJ.showMessage("About Inverter_...", "This sample plugin filter inverts 8-bit\n"+
"images. Look at the Inverter_.java source file to see how easy it is in \n"+
"ImageJ to process non-rectangular ROIs, to process all the slices in a stack\n" +
"and to display an About box." );
}
}
4 On appelle « cast » transtypage en français.
Commentaire de code :
L'interface PlugInFilter a deux méthodes : set et run. La méthode set permet ici au plugin de
savoir sur quel type d'image il peut travailler et d'envoyer une fenêtre d'info sur le plugin.
Notez comme on récupère facilement, largeur de l'image, taille de la ROI et un tableau mono­
dimensionnel des pixels de l'ImageProcessor. On calcule un offset pour ne pas calculer la position de chaque retour à la ligne, on imbrique
alors deux boucles pour parcourir l'image.
2.3.Gestion des images
En regardant les plugins précédents, on s'aperçoit que les images sont représentées par les objets
ImagePlus et ImageProcessor. Quelle est la différence et qu'en est­il réellement ?
2.3.1.Types d'images
Vous savez maintenant qu' ImageJ est capable de gérer plusieurs types d'images :
●
8 bits niveaux de gris
●
8 bits couleur avec table de couleur
●
16 bits niveaux de gris
●
RVB couleur chaque couche étant en 8 bits
●
32 bits niveaux de gris
ImagePlus est un objet qui représente une image. Il est basé sur un ImageProcessor, classe qui
manipule le tableau de pixels et traite donc réellement l'image. Le type d' ImageProcessor utilisé
dépend du type d'image. Ces types d'images sont représentées par des constantes dans ImagePlus :
●
COLOR_256
●
COLOR_RGB
●
GRAY16
●
GRAY32
●
GRAY8
Je vous laisse deviner à quel type correspond chaque constante ...
Évidemment, puisque chaque image est manipulée directement par un ImageProcessor, on peut
récupérer ou initialiser le type d' ImageProcessor avec des méthodes d' ImagePlus :
ImageProcessor getProcessor()
void setProcessor(java.lang.String title, ImageProcessor ip)
Lorsqu'on travaille avec des filtres, il est inutile de s'occuper du type de processor de l'image en
cours car il est passé en argument à la méthode run. ImageProcessor est une classe abstraite dont
dérivent cinq sous­classes :
ByteProcessor Utilisé pour les images 8 bits couleurs ou niveaux de gris. Possède une sous­classe
nommée BinaryProcessor pour des images en niveau de gris qui ne contiennent que les valeurs 0 et
255.
ShortProcessor Pour les images 16 bits (niveau de gris).
ColorProcessor Pour les images couleurs 32 bits ­type entier­ (RVB 8 bits par canal).
FloatProcessor Pour les images 32 bits ­type flottant­ niveaux de gris , haute précision.
2.3.2. Récupérer les pixels
Pour travailler sur les valeurs de l'image, il faut récupérer la valeur de chaque pixel, on peut utiliser
pour cela la méthode java.lang.Object getPixels() de l' ImageProcessor. On fera par exemple :
int[] mes_pixels = (int[]) monProcessor.getPixels();
pour une image couleur (le type est integer = entier). Ici, on déclare un tableau d'entiers qui
s'appelle mes_pixels et qui prend pour valeurs les valeurs de pixels de monProcessor. Notez que
nous effectuons un transtypage pour être certain de ramener des valeurs entières. On récupère donc
ici un tableau mono­dimensionnel. Il faudra récupérer largeur (et hauteur) de l'image pour pouvoir
passer d'un tableau mono­dimensionnel à des coordonnées XY (rappelez­vous des deux boucles
imbriquées).
Attention : Il existe un piège avec la récupération de trois types de processor : ByteProcessor,
ShortProcessor et ColorProcessor. Le type de donnée byte en Java est signé, c'est­à­dire qu'il est compris entre ­128 et 127. On attend
plutôt d'une image que ses valeurs soient comprises entre 0 et 255 (la lumière est plus ou moins
intense ou au pire absente). Quand on fait un transtypage d'un byte, il vaut mieux s'assurer que le
signe soit éliminé. Cette opération peut se faire grâce à l'opérateur ET binaire ( & ) :
int pix = 0xff & pixels[i];
...
pixels[i] = (byte) pix;
Même combat avec le type short ­32768 à 32767 en java. Il faut donc faire :
int pix = pixels[i] & 0xffff;
...
pixels[i] = (short) pix;
pour avoir des valeurs entre 0 et 65535.
C'est encore un peut plus compliqué avec le type entier (les images couleur) car les trois
composantes R, V et B sont empaquetées dans la même valeur entière :
int red = (int)(pixels[i] & 0xff0000)>>16;
int green = (int)(pixels[i] & 0x00ff00)>>8;
int blue = (int)(pixels[i] & 0x0000ff);
...
pixels[i] = ((red & 0xff)<<16)+((green & 0xff)<<8) + (blue & 0xff);
En plus de la vérification du signe nous utilisons des opérateurs binaires de rotation de bits.
2.3.3.Créer des images
ImagePlus createImagePlus(); // ou
ImageProcessor createProcessor(int width, int height);
Les méthodes ci­dessus répondent à nos interrogations sur la manière de créer une image. On
pourra en fixer le type dans un second temps à l'aide de différents arguments. Explorer l'API pour
les connaître ...
Vous pourrez créer une copie de l'image courante en créant un image puis recopiant les pixels avec :
void copyBits(ImageProcessor ip, int xloc, int yloc, int mode);
Le mode est contenu dans l'interface Blitter et correspond à une des valeurs suivantes :
ADD destination = destination+source
AND destination = destination AND source
AVERAGE destination = (destination+source)/2
COPY destination = source
COPY_INVERTED destination = 255­source
COPY_TRANSPARENT Les pixels blancs sont interprétés comme transparents.
DIFFERENCE destination = |destination­source|
DIVIDE destination = destination/source
MAX destination = maximum(destination,source)
MIN destination = minimum(destination,source)
MULTIPLY destination = destination x source
OR destination = destination OR source
SUBTRACT destination = destination – source
XOR destination = destination XOR source
2.3.4. Afficher les images
ImageJ affiche les images en utilisant une classe nommée ImageWindow. C'est la fenêtre qui
contiendra l'image ImagePlus, la classe contient tout ce qui est nécessaire à mettre à jour et afficher
les images nouvellement créées.
2.3.5. Quelques méthodes utiles relatives à l'image
Je vous livre en vrac quelques méthodes qui vous seront utiles pour écrire vos premiers plugins
mais la liste est loin d'être exhaustive. Si vous en voulez plus, consultez l'API, comme d'habitude.
ImagePlus
void setWindow(ImageWindow win); définit la fenêtre qui affiche l'image.
ImageWindow getWindow(); récupère la fenêtre qui affiche l'image.
void mouseMoved(int x, int y); affiche la position de la souris et la valeur du pixel dans la « status
bar ».
LookUpTable createLut(); créée une table de couleur à partir de l'image courante.
ij.process.ImageStatistics getStatistics(); équivaut à « measure » dans le menu d'ImageJ.
ij.measure.Calibration getCalibration(); retourne la calibration de l'image.
ImageProcessor
void flipHorizontal() et void flipVertical() retournement horizontal et vertical.
void rotate(double angle) rotation d'un certain angle.
void scale(double xScale, double yScale) redimensionnement.
ImageProcessor crop() découpe l'image selon la ROI courante. Renvoie une nouvelle image.
ImageProcessor resize(int dstWidth, int dstHeight) redimensionnement.
ImageProcessor rotateLeft() et ImageProcessor rotateRight() rotations horaire et anti­horaire.
void setInterpolate(boolean interpolate) Si true, crop(), resize() et rotate() utiliseront une
interpolation bilinéaire et une simple interpolation en proches voisins si false.
void convolve3x3(int[] kernel) convolue l'image avec la matrice dont le noyau est passé en
argument.
void sharpen() et void smooth() comme dans le menu process d'ImageJ. Filtres 3x3.
void dilate(), void erode() et void findEdges() équivaut aux menus process > binary avec des filtres
respectivement minimum, maximum (3 x 3) et un opérateur de Sobel.
void medianFilter() médian 3x3.
void gamma(double value) correction de gamma.
void invert() inversion d'image.
void add(int value) ajout de constante à chaque pixel.
void multiply(double value) multiplie chaque pixel par l'argument.
void and(int value) ET binaire avec l'argument.
void or(int value) OU binaire avec l'argument
void xor(int value) OU exclusif avec l'argument.
void log() recalcule les valeurs de pixels sur une échelle logarithmique.
void noise(double range) ajoute du bruit aléatoire (dans l'intervalle range) dans l'image
void setColor(java.awt.Color color) couleur d'avant­plan.
void setValue(double value) couleur de remplissage.
void setLineWidth(int width) largeur de ligne.
void moveTo(int x, int y) position de départ du tracé
void lineTo(int x2, int y2) trace une ligne jusqu'aux coordonnées en argument.
void drawPixel(int x, int y) colorie le pixel en x,y avec la couleur de tracé en cours.
void drawDot(int xcenter, int ycenter) tracé d'un point.
void drawDot2(int x, int y) tracé d'un point 2x2.
void fill() emplit la ROI rectangulaire de la couleur de tracé en cours.
void fill(int[] mask) emplit les pixels de la ROI qui font partie du masque avec couleur en cours.
void drawString(java.lang.String s) Dessine la chaîne s à la position courante.
int getStringWidth(java.lang.String s) retourne la largeur de la chaîne en pixels.
double getMin() et double getMax() retournent les plus petite et grande valeurs affichées.
void setMinAndMax(double min, double max) étale les valeurs de pixel dans la largeur du type
d'image en cours entre min et max.
void resetMinAndMax() pour les images short et float (16 et 32 bits), recalcule le min et max pour
un affichage correct (255 niveaux possibles).
void autoThreshold() seuil automatique.
double getMinThreshold() et double getMaxThreshold() seuils inférieur et supérieur.
void setThreshold(double minThreshold, double maxThreshold, int lutUpdate) int[] getHistogram() retourne l'histogramme de l'image. Ce sera un histogramme de luminosité pour
les images couleurs et la valeur null pour les 32 bits.
int getHistogramSize() La taille est de 256 pour les images 8 bits ou couleur, MinMax+1 pour les
16 bits.
void snapshot() sauvegarde l'état du processor en cours.
java.lang.Object getPixelsCopy() retourne une référence au snapshot, donc au tableau de pixel avant
la dernière modification.
void reset() ré­initialise l'image à l'état du snapshot.
void reset(int[] mask) idem sauf pour les pixels qui font partie du masque.
Stacks
java.lang.Object[] getImageArray() renvoie le stack comme un tableau d'ImagePlus.
boolean isHSB() et boolean isRGB() renvoie true si c'est un stack de 3 images HSB ou RGB. 2.3.6.Quelques autres méthodes utiles
Erreurs
static void error(java.lang.String msg) affichage d'un message avec « Error ».
static void showMessage(java.lang.String msg) affichage d'un message.
static void showMessage(java.lang.String title, java.lang.String msg) affichage d'un message dont
on spécifie le titre de fenêtre.
static boolean showMessageWithCancel(java.lang.String title, java.lang.String msg) affichage d'un
message dont on spécifie le titre de fenêtre et qui renvoie false si l'utilisateur clique sur Cancel, true
sinon.
static void noImage() affichage d'un message d'erreur « no images are open »
static void outOfMemory(java.lang.String name) affichage d'un message d'erreur « out of memory »
static boolean versionLessThan(java.lang.String version) renvoie un message d'erreur et retourne
false si la version d'ImageJ est inférieure à celle spécifiée.
Texte
static void write(java.lang.String s) écrit s dans la fenêtre de résultat.
static void setColumnHeadings(java.lang.String headings) définit les en­têtes de la fenêtre de
résultat.
static java.lang.String d2s(double n) convertit un nombre en chaîne, garde 2 chiffres après la
virgule.
static java.lang.String d2s(double n, int precision) convertit un nombre en chaîne, garde
« precision » chiffres après la virgule.
Barre de statut et de progression
static void showStatus(java.lang.String s) écrit dans la barre d 'état.
static void showTime(ImagePlus imp, long start, java.lang.String str) affichage du temps écoulé
depuis start et du taux de pixels traité à la seconde.
static void showProgress(double progress) met à jour la valeur de la barre de progression (compris
entre 0.0 et 1.0).
static double getNumber(java.lang.String prompt, double defaultNumber) pour récupérer un
nombre auprès du gentil utilisateur.
static java.lang.String getString(java.lang.String prompt, java.lang.String defaultString) pour
récupérer une chaîne auprès du gentil utilisateur.
Appel de commandes
static void doCommand(java.lang.String command) lance une commande dans un thread séparé.
static void run(java.lang.String command) lance une commande. Le programme est bloqué jusqu'à
la fin de cette commande.
static java.lang.Object runPlugIn(java.lang.String className, java.lang.String arg) lance un
plugin.
static void beep() émission d'un bip.
static boolean altKeyDown() et static boolean spaceBarDown() renvoie true si Alt ou Espace sont
enfoncées.
Mise en application
import ij.*;
import ij.plugin.PlugIn;
public class Message_Test implements PlugIn {
public void run(String arg) {
IJ.showStatus("Plugin Message Test started.");
IJ.showProgress(0.0);
IJ.error("I need user input!");
String name = IJ.getString("Please enter your name: ","I.J. User");
IJ.showProgress(0.5);
IJ.write("Starting sample plugin Hello ... ");
IJ.runPlugIn("Hello_","");
IJ.showProgress(1.0);
IJ.showMessage("Finished",name+", thank you for running this plugin");
}
}
Conclusion
En quelques pages, nous avons survolé l'énoooorme API d'ImageJ. Toutes les réponses à vos
questions sont dans la documentation de l'API normalement. Attention, ImageJ évolue très vite :
Version 0.50, 23­Sep­97, Version 0.99, 2­March­99, Version1.01, 25­March­99, Version 1.10, 8­
Nov­99,Version 1.20, 20­Dec­2000, Version 1.30v, 3 July 2003, Version 1.31v, 4­Fev­2004,
Version 1.32j, 12­Mai­2004. Il sort une révision mineure tous les 8 à 15 jours.
Cela ne remet pas en cause vos anciens plugins en général. Vous devrez peut­être faire quelques
modifications mineures pour qu'ils continuent à fonctionner.
On imagine qu'ImageJ va maintenant se simplifier, il y aura, à terme, une unification des méthodes
d'accès aux images indépendemment de leur type comme avec le package ImageAccess de l'école
polytechnique de Lausanne par exemple.
Dans tous les cas, l'accès au code source du programme permet d'être sûr d'une chose : ImageJ
deviendra ce que NOUS en ferons ...

Documents pareils