La partie 3 du TP (section 4) est à rendre pour le lundi 14 décembre à 8h00 (matin).
Pour ce TP comme pour les suivants, la pièce la plus importante sera un fichier tp2-nom-prenom.py
contenant votre programme. Le seul fait que votre programme fonctionne ne suffit pas pour avoir une bonne note. Les points suivants seront pris en compte :
Certaines questions appellent à une réponse que vous pouvez mettre en commentaire dans votre fichier tp2-nom-prenom.py
. Si vous le souhaitez vraiment, vous pouvez aussi m'envoyer un rapport de TP. Le format de ce rapport sera au choix, mais par ordre de préférence :
.odt
)
Attention : les points suivants ne rapportent rien, mais ne pas les respecter pourra retrancher jusqu'à 10 points sur la note finale :
Carte d'altitude du Grand Canyon |
Carte d'altitude du Pic Emory (en niveau de gris) |
Le format de fichier « USGS-DEM
» est un format pour les données altimétriques (DEM
: « Digital Elevation Model ») utilisé par l'institut « US Geological Survey ». En gros, chaque fichier contient les données d'altitude pour un morceau de terrain.
Voici par exemple un tels fichier : DEM pour une partie du Grand Canyon. Comme vous pouvez le constater, ce fichier comporte une unique ligne. (Ceci vient du fait que ce format de fichier était surtout utilisé sur des bandes magnétiques, qu'on accédait donc au données de manière purement séquentielle. Découper le fichier en plusieurs lignes n'était pas forcement utile...)
Le tout début de la ligne contient des informations sur le lieu en question : c'est simplement du texte, et il se trouve entre le début de la ligne et la colonne 134. (Voir la page wikipedia pour un résumé.) Viennent ensuite quelques informations supplémentaires (voir les pages 28 et suivantes du document Standards for Digital Elevation Models pour les détails), puis les coordonnées x (« easting ») et y (« northing ») des coins du rectangle de terrain.
Les informations suivantes sont :
delta_x
et delta_y
),
delta_z
),
Toutes ces informations sont à des emplacements fixes. Par exemple, l'altitude minimal d'un morceau de terrain se trouve toujours entre les octets 738 et 761.
Cette entête se trouve obligatoirement sur un bloc de 1024 octets. (Ça veut dire par exemple que la fin des 1024 octets est remplie par des espaces...)
Les nombres entiers sont écrits avec la notation usuelle et les nombres flottants utilisent la notation FORTRAN. (Voir plus bas.)
L'entête « A
» se termine à l'octet 915. Viennent ensuite tout un ensemble de « colonnes » de données. Chacune de ces colonnes s'appelle un « bloc B
» et commence par quelques informations dont voici les plus importantes :
La suite de chaque « bloc B
» (à partir de l'octet 144) contient des entiers pour l'altitude relative à l'altitude du premier point des points de la colonne. Quand toutes les altitudes ont été données, un nouveau « champ B
» commence. Ces altitudes sont données en unité delta_z
dont la valeur est donnée dans l'entête A
du fichier. L'altitude réelle d'un point est donc obtenu par altitude_premier_point + delta_z*alt
.
Les détails sur ce « bloc B
» sont disponibles aux pages 41 et 42 du document Standards for Digital Elevation Models.
La totalité d'un « bloc B
» est stocké sur un nombre d'octets multiple de 1024. Il peut donc y avoir des espaces supplémentaires en fin de « bloc B
» avant de passer au « bloc B
» suivant (ou au « bloc C
»).
Le fichier se termine éventuellement par un « champs C
» que l'on peut ignorer. (Voir p 44 du document Standards for Digital Elevation Models.) Ce bloc est stocké sur 1024 octets.
Vous pouvez choisir les fichiers DEM qui vous intéressent à partir d'ici, ou bien utiliser directement un des fichiers pour le Grand-Canyon :
Pour gagner de la place, les fichiers sont uniquement téléchargeables au format compressé. Pour les utiliser, vous pouvez soit les décompresser à la main ($ gunzip ./grand_canyon-w.gz
dans un terminal), ou bien laisser Python le faire pour vous. Cette seconde option est meilleure car si vous voulez traiter de nombreux fichiers, vous n'aurez pas besoin de tous les décompresser à la fois. (Par exemple, pour traiter toute la Californie, les fichiers décompressés utilisent 659Mo alors que les mêmes fichiers compressés n'utilisent que 97Mo.)
Pour utiliser les versions compressés des fichiers, il suffit de remplacer la fonction open()
par la fonction gzip.open()
. (Il faut donc avoir un import gzip
au début de votre fichier.)
Si fd
est un descripteur de fichier obtenu grâce à un open
ou gzip.open
, on peut lire un nombre donné d'octet dans une chaîne de caractères avec la fonction fd.read
. Par exemple :
>>> fd = open("./donnees") >>> huit_octets = fd.read(8)
Pour un fichier texte « normal » octet correspond exactement à un caractère. (Ce n'est plus vrai pour les fichiers avec accents au format UTF8.)
Python utilise la « notation scientifique » pour les flottants :
-42
pour ... -42,
3.1415
pour 3,14,
0.01e2
ou 0.01E2
pour 1,
Si une chaîne de caractère contient un tels nombre, on peut la convertir au format flottant de python avec la fonction float()
. Par exemple :
>>> s = '314159265e-6' >>> pi = float(s) >>> print pi 3.14159265
Le format USGS-DEM
utilise la notation FORTRAN, ou l'exposant n'est pas indiqué par la lettre e
, mais par la lettre D
. Pour convertir une telle chaîne s
au format float, il faut donc commencer par remplacer dans s
chaque D
par un e
. Vous pouvez par exemple utiliser la fonction s.replace()
.
Écrivez le corps de la fonction suivante :
def fortran_float(s): """Transforme la chaîne de caractère "s" représentant un flottant au format FORTRAN en un flottant. Cette fonction ignore les caractères blancs au début et à la fin de la chaîne "s".""" ... ...
En plus des tableaux « normaux », Python offre la possibilité d'avoir des « tableaux associatifs » (aussi appelés « tables de hachages », « tables de hash » ou « map »). Ce sont un peu comme des tableaux, mais où on peut indexer les cases par (presque) n'importe quoi.
On initialise un tableau associatif avec le tableau associatif vide : assoc = {}
, et on ajoute / modifie des éléments avec assoc[index] = valeur
. Pour obtenir la valeur associée à un index, on peut utiliser assoc[index]
, mais cela provoque une erreur s'il n'y a pas de valeur stockée à cet index. Le plus simple est d'utiliser assoc.get(index, valeur_par_defaut)
.
Par exemple :
>>> information = {} >>> information["alt max"] = 4807 >>> print information { 'alt max': 4807 } >>> h = information["alt max"] >>> print h 4807 >>> b = information["alt min"] # Erreur Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'alt min' >>> b = information.get("alt min",0) >>> print b 0
Un fichier python peut être utilisé de deux façons : soit comme une bibliothèque (c'est ce qui se passe quand vous faites un import math
), soit comme un programme principal (c'est ce qu'il se passe quand vous tapez F5
dans Idle).
Pour pouvoir utiliser un fichier comme bibliothèque, il faut éviter que lors de l'import
, votre fichier fasse des choses comme utiliser des variables globales (c'est Mal) ou faire des entrées / sorties (print
et consorts). C'est seulement lorsqu'on l'utilise comme programme que votre programme devra faire tout ça.
Une manière de procéder est d'organiser son programme de la manière suivante :
# les bibliothèques dont j'ai besoin import ... import ... # mes fonctions intermédiaires def rectangle(...): ... ... def disque(...): ... ... # la fonction principale if __name__ == "__main__": ... ... ...
La ligne if __name__ == "__main__":
permet de tester si le fichier est utilisé comme programme principal (main
). Si on l'utilise comme une bibliothèque, alors ce qui se trouve après cette ligne ne sera pas exécuté.
Écrivez votre fichier (ou vos fichiers) de cette manière.
Le but des prochains TP sera de transformer un (ou plusieurs) fichiers au format USGS-DEM
en un fichier image comme ceux qui se trouvent au début du sujet.
Pour commencer, nous allons juste regarder les entêtes de type A
du fichier :
Écrivez la fonction suivante :
def recupere_entete_A(fd): """Récupère les informations intéressantes contenues dans l'entête A d'un fichier DEM. L'argument "fd" est le descripteur de fichier correspondant au fichier DEM. La fonction renvoie un tableau associatif contenant au moins les champs suivants : - un champs 'description' (chaîne de caractères) - des champs 'coin_NO', 'coin_NE', 'coin_SW' et 'coin_SE' qui contiennent les coordonnées (paires de flottants) des 4 coins de la région correspondante au fichier - des champs 'altitude_max' et 'altitude_min' (flottants) - un champs 'nb_colonnes' qui contient le nombre de colonnes de données contenu dans la suite du fichier (entier). À la fin de la fonction, le descripteur de fichier devra être en position pour commencer à lire l'entête du premier champ B du fichier.""" ... ... ...
Le descripteur de fichier fd
aura été obtenu grâce à un open()
ou un gzip.open()
. Remarquez que cette fonction est à la fois une fonction (elle renvoie une valeur) et une procédure (elle modifie le descripteur de fichier). Pour la tester, utiliser votre fichier comme programme principal.
Écrivez la fonction suivante :
def affiche_info_entete_A(info): """Affiche de manière lisible l'information de l'entête A d'un fichier DEM. Les informations sont données en argument à travers le tableau associatif "info". (Voir la fonction recupere_entete_A() pour le contenu exact de ce tableau.)""" ... ... ...
Écrivez la fonction suivante :
def recupere_entete_B(fd): """Récupère les informations dans l'entête d'un bloc de type B dans un fichier DEM. L'argument "fd" est un descripteur de fichier se trouvant au début d'un bloc de type B. La fonction renvoie un tableau associatif qui contient au moins les champs - 'colonne' pour indiquer le numéro de la colonne contenu dans ce bloc, - 'nb_lignes' pour indiquer le nombre de lignes dans ce bloc, - 'altitude_initiale' pour l'altitude du premier point de ce bloc, - 'coord' pour les coordonnées du premier point de ce bloc, - 'altitude_min' et 'altitude_max' pour les min / max locaux à ce bloc. Après cette fonction, le descripteur de fichier devra se trouver en position pour commencer à lire les données du bloc.""" ... ... ...
Bien sûr, comme précédemment,
Écrivez la fonction suivante :
def affiche_info_entete_B(info): """Affiche de manière lisible l'information d'un bloc B d'un fichier DEM. Les informations sont données en argument à travers le tableau associatif "info". (Voir la fonction recupere_entete_B() pour le contenu exact de ce tableau.)""" ... ... ...
Après chaque « entete B
» viennent les altitudes relatives de tous les points de la colonne correspondante. Le nombre de points est donnée dans l'entête B
aux octets 12--17. Chaque altitude est un nombre entier, et pour obtenir l'altitude réelle du point, il faut :
delta_z
qui se trouve dans l'entête A
, (en général 1 mètre, mais ce n'est pas garanti),
B
.
Ceci donne l'altitude réelle du point qui doit donc se trouver entre l'altitude minimale et maximale globales (dans l'entête A
) et locales (dans l'entête B
).
Écrivez la fonction suivante :
def altitudes_bloc_B(fd, entete_A, entete_B): """Récupère les données d'altitude d'un bloc B d'un fichier DEM. L'argument "fd" est un descripteur de fichier positionné au début des données du bloc B en question. L'argument "entete_A" est un tableau associatif contenant les informations de l'entête du fichier DEM, et l'argument "entete_B" est un tableau associatif contenant les informations relative à ce bloc. Cette fonction devra renvoyer un tableau d'altitude réelles : une case par ligne. Si jamais le bloc contient une incohérence (altitude plus grande que la maximum local par exemple), la fonction provoquera une erreur. Après la fonction, le descripteur de fichier devra se trouver au début du bloc B suivant (s'il existe).""" ... ... ...
Détaillez bien votre fonction en expliquant ce que vous faites et pourquoi vous le faites...
Pour cette fonction, je vous conseille de provoquer une erreur explicative lorsque vous rencontrer une incohérence. Par exemple :
if alt > alt_min: # incohérence ! raise Exception('Altitude (%f) plus grande que l'altitude minimale locale (%f)' % (alt, alt_min))
Le raise Exception(...)
permet de provoquer volontairement une erreur (une « exception »), le 'Altitude (%f) ...
permet de donner un message explicatif pour cette exception...
Une fois que vous disposer des altitudes réelles d'une colonne, il est assez facile de dessiner les pixels correspondants. Pour commencer, nous allons créer une image de dimension maximale, càd nb_colonne
(entete A
) par nb_lignes
pixels (entete B
). Ainsi, le point j
dans la colonne i
correspondra exactement au pixel (i,j)
.
Écrivez la fonction
def image_altimetrie_1 (fichier_DEM, nom_image): """Cette fonction crée une image (dont le nom est donné par l'argument "nom_image") des données altimétriques contenues dans le fichier DEM "fichier_DEM")""" ... ... ...
Bien entendu, cette fonction fera appel aux fonctions définies précédemment...
(0,0,0)
pour le point le plus bas et blanc `(255,255,255)`` pour le point le plus haut. Le points intermédiaires auront un gris (k,k,k)
proportionnel à leur dénivelé par rapport au point le plus bas.
Il s'agit du dernier TP : c'est en grande partie sur ce que vous me rendrez que sera basée votre note. N'hésitez pas à améliorer le début de votre programme...
En plus de votre fichier tp2-nom-prenom.py
(qui fonctionne), je demande un fichier supplémentaire manuel-nom-prenom
. Ce fichier contiendra un petit manuel d'utilisation de votre programme : il décrira
Ce fichier pourra être :
Écrivez maintenant une fonction image_altimetrie_2
qui permet de créer une image altimétrique à partir d'un fichier DEM mais qui comporte des arguments supplémentaires permettant :
0
correspondra alors au point le plus bas et la couleur 255
au point le plus haut. Pour obtenir des niveaux de gris, il suffit de donner le tableau [(0,0,0), (1,1,1), ..., (255,2 55,255)]
. (Les images de type "PNG" ont la possibilité d'avoir une palette, càd un tels tableau stocké dans l'image. On peut alors donner le numéro de la couleur plutôt que la couleur elle même...)
Donnez des valeurs par defaut aux arguments supplémentaires pour que l'on retrouve la fonction image_altimetrie_1
si on ne donne pas les arguments supplémentaires.
Notez bien les points suivants :
taille
à 256
, on doit obtenir une petite image de tout le Grand Canyon (et pas seulement un coin).
taille
, alt_min
, alt_max
, ... sont tous optionnels : si on ne les fournit pas à la fonction, il faut leur donner des valeur par defaut pertinentes.
Si Python est disponible sur l'ordinateur (assez courant), il est possible de lancer votre programme sans passer par Idle (qui n'est pas installé partout). La manière la plus simple de faire ceci est
#!/usr/bin/env python #encoding: utf8tout en haut de votre fichier.
tp2-nom-prenom.py
, de rendre votre fichier exécutable :
$ cd info-719/tp2 $ chmod 755 tp2-hyvernat-pierre.py
$ ./tp2-hyvernat-pierre.py ... ...
Le point 2 n'a besoin d'être fait qu'une seule fois.
Si vous copier votre fichier dans un répertoire système, ou si vous modifier votre variable d'environnement $PATH
, vous n'avez même pas besoin de vous mettre dans le répertoire qui contient votre fichier...
Essayez de lancer votre programme de cette manière.
Le résultat de l'opération devrait être exactement le même que si vous aviez exécuté votre programme à partir de Idle (touche F5
).
Il y a plusieurs avantages à lancer votre programme de cette façon :
Voici par exemple une exécution de mon programme :
$ ./hyvernat.py -h Utilisation : ./hyvernat.py fichier pour créer une image à partir du fichiers DEM 'fichier' Arguments optionnels : -h --help affiche ce message d'aide -i --image=nom spécifie le nom de l'image produite (défaut : fichier.png) -x --ecrase ne renomme pas le fichier image si celui ci risque d'écraser un ficher existant --largeur=l fixe la largeur de l'image --amin=a fixe une altitude minimale globale de 'a' --amax=a fixe une altitude maximale globale de 'a' --palette=p fixe une palette de couleurs pour l'image finale les palettes possibles sont : . 0 : niveaux de gris (defaut) . 1 : niveaux de gris, inverse . 8 : fausses couleurs . le nom d'une image palettisée pour récupérer une palette existante -m --marge=m fixe la taille, en pixels, de la marge -v --verbose affiche un peu plus d'information
Le programme Python hyvernat.py
a été lancé avec un unique argument optionnel : -h
. Cet argument optionnel provoque juste l'affichage d'un message d'aide qui explique comment on peut utiliser le programme.
Voici un autre exemple d'exécution plus intéressant :
./hyvernat.py -i carte-grand-canyon.png --largeur 256 --marge 10 -v grand_canyon-w.gz ======================================== Entête A du fichier : description : "GRAND CANYON - W AZ" -- "NJ12-10W" unité de mesure pour les coordonnées : arc-seconde unité de mesure pour les altitudes : metre coins de la région : (-410400.00,129600.00)--(-410400.00,133200.00)--(-406800.00,133200.00)--(-406800.00,129600.00) surface : 3600 x 3600 angle = 0.000000 altitude minimale = 278.00 altitude maximale = 2513.00 (lignes, colonnes) = (1,1201) delta_x = 3, delta_y = 3 et delta_z = 1 Image "carte-grand-canyon.png" créée avec succès en 2.11 secondes. ========================================
Les arguments optionnel ont permis :
Le point important est qu'à aucun moment il n'a été besoin de modifier le fichier hyvernat.py
.
On peut récupérer les arguments optionnels en utilisant le tableau argv[]
qui est initialisé par Python. Dans l'exemple précédent, argv[]
prenait la valeur "[ '-i', 'carte-grand-canyon.png', '--largeur', '256', '--marge', '10', '-v', 'grand_canyon-w.gz']
".
Pour avoir accès à ce tableau, il faut utiliser la bibliothèque sys
:
... import sys ... if __name__ == '__main__': print sys.argv
Pour traiter « simplement » ces arguments optionnels, le mieux est d'utiliser la fonction getopt
de la bibliothèque getopt
. Voici par exemple un morceau de mon propre programme :
import getopt ... opts, args = getopt.getopt(sys.argv[1:], "hi:vm:", ["largeur=", "amin=", "amax=", "marge="]) ...
Cette ligne permet de mettre dans le tableau opts
les couples (argument, valeur)
pour les arguments optionnels -h
, -i
, -v
, -m
en --largeur
, --amin
, --amax
, --marge
. Les « :
» ou « =
» permettent de préciser que certains arguments optionnels attentent une valeur juste derrière (cf. l'exemple ci dessus).
Le reste du tableau argv[]
se retrouve ensuite dans la variable args
. Dans l'exemple ci dessus, args
contient donc seulement grand_canyon-w.gz
.
Dans l'exemple ci dessus, opts
prenait la valeur "[('-i', 'carte-grand-canyon.png'), ('--largeur', '256'), ('--marge', '10'), ('-v', '')]
" et args
la valeur "['grand_canyon-w.gz']
".
Ajouter la possibilité de donner des arguments optionnels à votre programme :
-h
et --help
pour avoir un petit message d'aide
--amin
et --amax
pour spécifier le l'altitude minimale et maximale
--largeur
-v
et --verbose
pour afficher des informations pendant l'exécution
Ajoutez la possibilité de coller plusieurs images entre elles, comme pour les exemples au début du sujet de ce TP...
Si vous n'y arrivez pas, essayer de décrire comment vous vous y prendriez.