Audela |
![]() |
Tcl est un langage script interprété, ce qui le distingue des langages courants tels que C, Java, etc. Le langage Tcl est très efficace pour écrire des scripts, c'est à dire des enchaînements d'actions à faire. Or ces enchaînements d'actions élémentaires sont légions lorsque l'on observe en astronomie CCD. Ecrire un script Tcl sert donc, en premier lieu, à automatiser des séquences répétitives. En second lieu, nous découvrirons aussi que Tk, la boite à outil de Tcl, permet aussi de dessiner entièrement une interface. Cette dernière partie sera abordée dans une autre page. Comme tout langage, Tcl a sa syntaxe, ses avantages et ses inconvénients. Ce texte a pour but de présenter le plus simplement possible les bases de ce langage.
Le moteur de Tcl est son interpréteur. L'interpréteur Tcl analyse une ligne de texte, la décode et l'exécute. Ces trois actions se font séquentiellement. Prenons un exemple. La ligne suivante :
set a 5
est analysée par l'interpréteur Tcl de la façon suivante :
La ligne est divisée en mots séparés par des blancs,
Le premier mot désigne le nom de la fonction. La fonction set existe bien dans l'interpréteur Tcl et sert à assigner l'argument qui suit comme une variable dont la valeur sera celle du second argument.
Le deuxième mot, la lettre a, est désigné comme une nouvelle variable.
Le troisième mot, le caractère 5 (et non pas le chiffre 5), est la valeur de la variable a.
Ainsi, le programmeur C aura compris que set a 5 est l'équivalent de strcpy(a,"5");
L'interpréteur Tcl ne connaît que les variables alphanumériques (l'équivalent des string ou char en d'autres langages de programmation). Pour transformer une variable Tcl en un nombre, on utilise la fonction expr :
expr $a+10
est analysée par l'interpréteur Tcl de la façon suivante :
La ligne est divisée en mots séparés par des blancs,
Le premier mot désigne toujours le nom de la fonction. La fonction expr existe bien dans l'interpréteur Tcl et sert à calculer l'expression numérique qui suit. Le résultat sera retourné comme une chaîne de caractères (toujours une variable alphanumérique).
Le deuxième mot, $a+10, contient un symbole $.
L'interpréteur Tcl va substituer le nom de la variable qui suit le symbole $a par sa valeur, c'est à dire le caractère 5.
Ainsi, l'argument qui suit la fonction expr est le mot "5+10"
La fonction expr prend ce mot "5+10", le convertit en nombres et opérateurs et effectue le calcul
Le résultat de l'opération est le mot "15". Si l'on voulait stocker le mot 15 dans une variable on aurait écrit :
set b [expr $a+10]
L'usage des crochets signifie à l'interpréteur d'effectuer le calcul de la fonction à l'intérieur des crochets avant d'exécuter la fonction set.
On rappelle que Tcl est un langage manipulant uniquement des variables de type chaîne de caractère. Le classique A$="Hello" en basic se traduit par set a Hello en Tcl. Voici quelques autres équivalences BASIC - Tcl :
| |
|
| a$ = "toto titi" | set a "toto titi" |
| b = c + cos(5) | set b [expr $c+cos(5) ] |
| if (b<5) then e$=a$+" tutu" endif | if {$b<5} { set e [append a " tutu"] } |
| for i=1 to imax f(i)=i endfor | for {set i 1} {$i<$imax} {incr i} { set f($i) $i } |
| l=len(a$) aa$=a$(i) a$=ucase$(a$) | set l [string length $a] set aa [string index $a $i] set a [string toupper $a] |
| call func1(a$) | func1 $a |
| sub func1(a$) print a$ endsub | proc func1 {a} { puts $a } |
| open "toto.txt" for output as #1 print #1, a$ close #1 | set handler [open "toto.txt" w] puts $handler $a close $handler |
| files * | glob * |
De même, les équivalences C - Tcl :
| |
|
| strcpy(a,"toto titi"); | set a "toto titi" |
| b = c + cos(5); | set b [expr $c+cos(5) ] |
| if (b<5) { sprintf(e,"%s tutu",a); } | if {$b<5} { set e [append a " tutu"] } |
| for (i=1;i<imax;i++) { f[i]=i; } | for {set i 1} {$i<$imax} {incr i} { set f($i) $i } |
| l=strlen(a) ; aa=a[i]; a=strupr(a); | set l [string length $a] set aa [string index $a $i] set a [string toupper $a] |
| func1(a); | func1 $a |
| void func1(char *a) printf("%s",a); } | proc func1 {a} { puts $a } |
| handler=fopen("toto.txt","wt"); fprintf(handler,"%s",a); fclose(handler); | set handler [open "toto.txt" w] puts $handler $a close $handler |
| pas d'équivalent en C Ansi | glob * |
Une ligne de commande Tcl est composée de mots. Le premier mot est toujours le nom d'une fonction. Les mots suivants sont toujours les arguments. Il n'y a jamais d'exception à cette règle. L'espace ou le Tab constituent la séparation entre les mots. Une ligne qui commence par # est suivie d'un commentaire qui ne sera pas interprété. Le retour chariot ou le ; constituent la séparation entre les lignes de commande. Par exemple, la ligne
set b 5 ; # blabla
est interprétée comme deux lignes. La première set b 5 et une seconde, # balbla qui est un commentaire.
Chaque ligne d'un script Tcl subit une étape de substitution avant d'être interprétée. Par exemple :
set b 5
set c $b
La première ligne affecte le mot "5" à la variable b. Dans la deuxième ligne, le symbole $ signifie qu'il faut substituer le mot qui suit par sa valeur : 5 dans le cas présent. Ainsi, la ligne s'écrit set c 5 après substitution. Etudions un cas un peu plus compliqué:
set a set
set b 5
$a c $b
La première ligne affecte le mot de trois lettres "set" à la variable a. La deuxième ligne affecte le mot "5" à la variable b. Dans la troisième ligne, les symboles $ signifient qu'il faut substituer les mots qui suivent par leur valeur : respectivement set et 5. Ainsi, la ligne s'écrit set c 5 après substitution. L'interprétation est alors effectuée et la variable c vaut 5. Le langage Tcl permet donc de substituer n'importe quel mot d'une ligne de commande, même le mot de la fonction. Il faut donc se souvenir qu'une ligne de commande commence toujours par les substutions avant l'évaluation. Cette façon de procéder peut être avantageusement exploitée par l'utilisation des crochets [].
Les crochets [] permettent d'évaluer le résultat de la chaîne concernée avant d'effectuer l'évaluation de la ligne. Ainsi, les trois lignes précédentes peuvent être écrites sur deux lignes:
set a set
$a c [$a b 5]
L'interpréteur Tcl commence par sustituer les mots commençant par $ par leur valeur. Après la phase de subtitution, la ligne "$a c [$a b 5]" devient "set c [set b 5]". Avant d'évaluer la ligne substituée, l'interpréteur analyse la présence des crochets et traite chaque expression entre crochet comme une ligne de commande. Après cette étape, la ligne est devenue "set c 5" et elle est alors évaluée.
Le langage Tcl n'utilise qu'un seul type de variable: les chaînes de caractères. Ceci permet une manipulation très simple. Par exemple :
set a hello
set b Audela
set c "$a $b"
La variable c est la concaténation des mots "hello" et "Audela" (avec un espace entre les deux mots). Le langage Tcl permet d'écrire facilement du texte contenant des variables :
set infoversion "Vous utilisez Tcl version $tcl_patchLevel"
La variable tcl_patchLevel existe dans l'interpréteur Tcl dès sa création. D'autres variables contiennent diverses informations.
Rappelons que l'on peut évaluer une expression mathématique en utilisant la fonction expr. Par exemple :
set b 5
set d [expr sin($b)+2]
Les variables b et d sont des chaînes de caractères. La fonction expr traite ses arguments comme une expression mathématique à calculer numériquement.
Tcl contient des fonctions très puissantes pour traiter les chaînes de caractères. Il est souvent utile d'utiliser des listes de chaînes. Par exemple :
set a [list "tata" "titi" "tutu"]
Cette ligne est interprétée de la façon suivante. L'interpréteur va d'abord évaluer l'expression entre crochets
list "tata" "titi" "tutu"et va donc créer une liste contenant trois éléments. Le premier élément est tata, le deuxième est titi et le troisième est tutu. Cette liste est ensuite stockée dans la variable a (set a ). Les fonctions de liste permettent d'ajouter, de retirer, de trier, de remplacer, de rechercher, etc. Par exemple, la fonction lindex permet d'extraire un élément à une liste :
set b [lindex $a 0]
Cette ligne applique la fonction lindex sur la liste $a. Le mot 0 (zéro) représente la valeur de l'indice de l'élément à extraire : par convention 0 pour le premier élément. Ainsi, la variable b prendra la valeur tata. Pour ajouter un élément à la liste, on utilisera la fonction lappend :
set a [lappend $a "tyty"]
L'exemple ci dessous montre la puissance des fonctions de Tcl qui remplacent avantageusement (et sans bug !) de multiples lignes de Basic,
| |
|
| a$ = "toto titi" nwords=1 for k=1 to len$(a$) if (a$(k)=" ") then nwords=nwords+1; endif endfor tosearch="titi" for k=1 to len$(a$)-len$(tosearch) if (a$(k)=" ") then nwords=nwords+1 endif if (mid$(a$,k,k+len$(tosearch))=tosearch$) then index=nwords break endif endfor | set a [list toto titi] set nwords [llength $a] set tosearch "titi" set index [lsearch $a $tosearch] |
Les fonctions Tcl sont très nombreuses. Nous recommandons d'abord de lire les tutorials du site http://tcltk.free.fr ainsi que le cours d'Anne Possoz qui permettent de comprendre le B-A BA du Tcl/Tk. Citons aussi un livre en français : "Tcl/Tk précis&concis" éditions O'Reilly (http://www.oreilly.fr).
Sur le WEB, on peut consulter la liste exhaustive des fonctions disponibles
Enfin, le Guide de référence Tcl/Tk (pages 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) permet d'avoir la liste de toutes les fonctions dans un format très pratique pour consulter près du clavier. Attention il s'agit de la version 7.6. Audela utilise la version 8.0 qui est enrichie de quelques fonctions supplémentaires.
Lorsqu'un script devient complexe, il est utile de bien le structurer. Tcl permet de gérer les variables et les fonctions des scripts. La liste qui permet d'assembler de multiples valeurs entres-elles.
Les arrays ont à peu près la même fonction que les listes à cela près que l'indexation des éléments n'est pas ordinale (avec des chiffres) mais est nominale (avec des noms). Enfin, les fonctions peuvent elles aussi être assemblées sous forme de namespaces afin d'être repérables facilement.
Il est souvent utile de regrouper plusieurs variables dans une même catégorie. Par exemple, on souhaite calculer la masse d'air à partir des informations contenues dans l'entête FITS d'une image. Pour cela, on a besoin de la date de début de pose, du temps de pose, des coordonnées célestes de l'objet pointé et des coordonnées géographiques du lieu d'observation. La fonction "calcul_masse_dair" va donc avoir 6 paramètres :
proc calcul_masse_dair { date_obs exposure ra dec longitude latitude
} {
# --- date_obs : une liste {yyyy mm dd hh mm ss}en TU
# --- exposure : en secondes
# --- ra : en degrés
# --- dec : en degrés
# --- longitude : en degrés (négatif vers l'est)
# --- latitude : en degrés (négatif dans l'hémisphère sud)
#
# --- jour julien de debut de pose
set jj_debut_de_pose [mc_date2jd $date_obs]
# --- jour julien du milieu de pose
set jj_milieu_de_pose [expr jj_debut_de_pose+$exposure/86400./2.]
# --- mise en forme du lieu
if {$longitude<0} {
set longitude [expr -1.*$longitude]
set sens E
} else {
set sens W
}
set home "GPS $longitude $sens $latitude"
# --- calcul des paramètres altaz
set resultat [mc_radec2altaz $ra $dec $home $jj_milieu_de_pose]
# --- extrait la valeur de la hauteur sur l'horizon
set hauteur [lindex $resultat 1]
# --- calcul de la masse d'air
set masse_dair [expr 1./sin($hauteur)]
# --- retourne le résultat
return $masse_dair
}
mc_radec2altaz est une fonction de la librairie d'extension libmc. Les 6 arguments d'entrée sont lourds à manipuler. C'est pour soulager l'écriture que les "arrays" existent. Avant d'appeler la fonction, on enregistre la valeur des 6 paramètres dans un array que nous allons appeler info_image :
set info_image(date_obs) {2000 10 8 14 05 21}
set info_image(exposure) 60
set info_image(ra) 34.5678
set info_image(dec) 83.1235
set info_image(longitude) -2.8763
set info_image(latitude) 48.5423
proc calcul_masse_dair { info_image } {
# --- jour julien de debut de pose
set jj_debut_de_pose [mc_date2jd $info_image(date_obs)]
# --- jour julien du milieu de pose
set jj_milieu_de_pose [expr jj_debut_de_pose+$info_image(exposure)/86400./2.]
# --- mise en forme du lieu
if {$info_image(longitude)<0} {
set longitude [expr -1.*$info_image(longitude)]
set sens E
} else {
set longitude $info_image(longitude)
set sens W
}
set home "GPS $longitude $sens $info_image(latitude)"
# --- calcul des paramètres altaz
set resultat [mc_radec2altaz $info_image(ra) $info_image(dec)
$home $jj_milieu_de_pose]
# --- extrait la valeur de la hauteur sur l'horizon
set hauteur [lindex $resultat 1]
# --- calcul de la masse d'air
set masse_dair [expr 1./sin($hauteur)]
# --- retourne le résultat
return $masse_dair
}
Le regroupement des 6 paramètres dans un seul paramètre simplifie l'écriture de la fonction de calcul de la masse d'air. Si un jour vous enregistrez la valeur de la pression et de la température ambiante dans l'entête de l'image, il suffit d'ajouter deux éléments à l'array info_image, info_image(temperature) et info_image(pression) et de modifier le calcul de la masse d'air, à l'intérieur de la fonction calcul_masse_dair sans avoir à changer le nombre de paramètres d'entrée (ce qui évite une source d'erreur).
Les arrays sont très couramment employés dans l'interface Audace.
Pour les programmeurs C, les arrays s'apparentent beaucoup aux structures. Ainsi pourrait on transcrire l'array info_image sous la forme d'une structure en langage C :
struct {
char date_obs[255];
double exposure;
double ra;
double dec;
double longitude;
double latitude;
} info_image ;
Les arrays permettent de regrouper un ensemble cohérent de variables.Un namespace permet de regrouper un ensemble cohérent de fonctions de même catégorie. Le nom du namespace est toujours suivi de :: pour le distinguer. Les fonctions Tcl de base (set, for list, etc.) font partie du namespace :: et on devrait donc, en toute rigueur, parler des fonctions ::set, ::for, ::list ,etc. Pour ne pas alourdir la syntaxe, l'interpréteur Tcl accepte que l'on supprime les deux premiers double points.
Pour comprendre l'intérêt d'un namespace, prenons l'exemple de fonctions de traitement d'images que vous utilisez couramment : offset, sub, div, smedian, etc. Ces fonctions vous paraissent peut être insuffisamment complètes et vous aimeriez les enrober un peu pour vos besoins personnels. Idée recevable qui mérite l'usage d'un namespace. Nommons le trt (pour traitements) :
namespace eval ::trt {
proc subd { {filename dark.fit} } {
::sub $filename 0
::visu
}
proc divf { {filename flat.fit} } {
::div $filename 10000
::visu
}
}
Nous venons ainsi de créer deux fonctions qui seront appelées ::trt::subd et ::trt::divf (si on veut les utiliser depuis la console de Audace ou bien dans un script). En quoi ces fonctions ont-elles un intérêt ?
La fonction ::sub de l'interface Audace exige deux arguments d'entrée : un nom de fichier et la valeur d'une constante à ajouter aux pixels. Or vous avez l'habitude de vous servir de la fonction sub pour soustraire une image dark à votre image et de ne pas ajouter de constante. Par défaut, on a défini, dans le namespace, que la fonction ::trt::subd allait soustraire l'image "dark.fit" a celle en mémoire. Enfin, vous voulez visualiser l'image après le traitement (fonction ::visu de l'interface Audace). La fonction subd du namespace trt est donc facile d'emploi :
::trt::subd
C'est tout ! la soustraction du dark est réalisée et l'image est affichée à l'écran. La fonction ::trt::divf permet de diviser par un flat bien connu. Ces deux fonctions reposent sur les mêmes démarches et méritent d'être assemblées dans la même catégorie, c'est à dira au même namespace.
Afin de structurer les différentes fonctions d'un grand script, il est conseillé d'user des namespace. C'est ainsi que cela fonctionne dans les scripts de l'interface Audace.
Les programmeurs de C++ auront remarqué que le namespace est assez proche de la notion d'objet à cela près que la définition de la classe et l'instanciation de l'objet se passent en même temps dans un namespace.