Des spectres de raies avec Python

Des spectres de raies d’émission aussi beaux que dans les manuels (plus ?), projetables en classe, rigoureux et personnalisables : c’est possible avec ce petit programme Python.


En résumé :

  • Obtenir des données spectroscopiques de longueurs d’onde, pour un élément chimique.
  • Avec Python et ses modules pandas et matplotlib : tracer un graphique avec un trait vertical pour chaque longueur d’onde.
  • Mettre en forme le document pour qu’il ressemble à un spectre de raies. Notamment, utiliser un «colormap» qui attribue une couleur à une valeur de longueur d’onde.

Obtention des données

Il faut commencer par récupérer des valeurs de longueurs d’onde.

Le site du National Institute of Standards and Technology (www.physics.nist.gov/asd) met à disposition sa base de données.

Rendez-vous sur la page de formulaire : https://physics.nist.gov/PhysRefData/ASD/index.html (cliquer sur la case “LINES”)

Choisir un élément chimique, les bornes de longueurs d’onde, ainsi qu’une foule d’autres paramètres (certains assez obscurs…) En ce qui me concerne, j’ai décoché tout ce que je pouvais pour ne pas surcharger mes fichiers.

L’idée est de récupérer un fichier texte (.txt) ou .csv qui recense les longueurs d’ondes pour l’élément recherché. On peut aussi bien cocher la case «page html», d’où on peut faire un copier-coller des valeurs tabulées.

La colonne étiquetée «Aki» indique les intensités des raies. On conserve ces données afin de se laisser la possibilité de sélectionner les raies les plus intenses.

Copiez les valeurs dans un fichier texte (Sur Linux Ubuntu, on peut utiliser le programme Gedit, par exemple. Sur Windows : NotePad).

Enregistrez ce nouveau fichier sous le format .csv, exploitable par le module Python «pandas» pour extraire les données. Choisir un nom court, simple et univoque, pour le fichier (le nom de l’élément, par exemple).

«Nettoyer» le fichier .csv

Avant de s’attaquer à la rédaction du code, je conseille d’épurer le fichier .csv en retirant tout ce qui est inutile et/ou pourrait causer des tracas dans le traitement des données.

  • Donner un nom simple à la colonne des longueurs d’onde («wl» ou «lo», par exemple)
  • Supprimer les guillemets et les «=» autour des valeurs, sinon les valeurs seront interprétées comme des chaînes de caractères au lieu de nombres flottants.
  • Supprimer les colonnes inutiles (je n’ai utilisé que les longueurs d’onde et l’intensité).
  • Supprimer les lignes vides.

Certaines de ces tâches s’exécutent plus facilement avec le logiciel d’éditeur de texte (outil ‘Rechercher/remplacer”), d’autres plutôt en ouvrant le fichier .csv avec un tableur (comme LibreOffice). Elles seront à répéter pour tous les fichiers des éléments chimiques souhaités.

Exemple de contenu d’un fichier .csv pour le mercure :

 element ,wl,Aki
Hg II  ,350.9758,0.00042
Hg II  ,351.0625,0.048
Hg II  ,351.5164,0.0056
Hg II  ,351.5164,0.0002
Hg II  ,353.2594,0.3
Hg II  ,354.1352,0.0003
Hg II  ,354.9411,0.15
Hg II  ,360.2931,0.0098
Hg II  ,360.3908,0.02
Hg II  ,360.4085,0.14
Hg II  ,360.5762,0.3
Hg II  ,360.6885,0.057
Hg II  ,361.4875,0.0085

Les données d’une même ligne sont ici séparées par des virgules. Il faudra le préciser dans la commande Python.

Rédaction du code Python

#chargement des modules nécessaires :
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np#seulement pour changer l'intervalle de longueurs d'onde

#Extraction des données des fichiers CSV :

spectreHe=pd.read_csv("He.csv", delimiter=',')


#Filtrage des raies les plus intenses :

spectreHef=spectreHe[spectreHe["Aki"]>0.1*max(spectreHe["Aki"])]

La précédente ligne de code n’enregistre dans spectreHef que les données du tableau spectreHe dans lesquelles l’intensité (Aki) est supérieure à 0.1*la valeur maximale du tableau.

En augmentant le coefficient multiplicateur, on pourra facilement – après avoir visualisé le spectre – sélectionner les raies les plus intenses.

Le plus dur est fait. Il ne reste qu’à parcourir les longueurs d’onde du tableau de données, dans une boucle «for», et tracer pour chacune un trait vertical.

Pour répéter l’opération facilement avec plusieurs éléments, j’ai défini une fonction que j’appelle à chaque sous-graphique.

def trace(element, data):
    for L in data["wl"]:
        plt.plot([L, L], [0, h], linewidth=ep, c=cmap((L-350)/400))
#        plt.plot([L, L], [0, h], linewidth=ep, c='black')#raies en noir
        plt.ylabel(element, fontsize=18, rotation=0, ha='right')
        plt.yticks([])#supprime les graduations en y
        plt.xlim(350, 750)#limites de l'axe x
        plt.ylim(0, h)#limites de l'axe y
        #changer les intervalles de longueur d'onde :
#        plt.xticks(np.arange(400, 800, 100))#le dernier nombre est l'intervalle

Mise en forme

La fonctionnalité la plus intéressante consiste à utiliser un «colormap» (gamme de couleurs?).

Un “colormap” associe à une valeur donnée une couleur de tracé. Le “colormap ” «turbo» propose les couleurs approchées de celle du spectre de la lumière blanche.

“Jet” comporte trop de couleur cyan que l’on ne retrouve pas dans la lumière blanche. Attention, ‘turbo’ n’est inclus que dans les versions de Matplotlib postérieures à la version 3.3.0. Il vous faudra peut-être mettre à pour Matplotlib pour l’obtenir. À défaut, modifiez le code en remplaçant ‘turbo’ par ‘jet’.

cmap=plt.get_cmap('turbo')#gamme de couleurs du visible

Dans la commande qui trace une raie, on indique dans les arguments que la couleur du trait vertical est donnée par :

c=cmap((L-350)/(400))

où L est la longueur d’onde.

Le principe d’un colormap est que la première couleur est associée à la plus faible valeur de la gamme (pour moi : 350 nm), et ainsi de suite.

Je divise par 400 car c’est l’intervalle entre les valeurs minimale (350 nm) et maximale (750 nm), pour que le résultat soit nécessairement compris entre 0 et 1.

En toute rigueur, il existe une manière de normer un colormap, pour faire cet ajustement de manière automatique. N’ayant pas réussi à adapter les exemples trouvés sur le web, je me suis rabattu sur cette normalisation assez simple. Si vous connaissez mieux, vos commentaires sont les bienvenus.

Le résultat n’est pas parfait : la correspondance longueur d’onde ↔ couleur n’est qu’approximative, mais tout à fait acceptable.

Avec une figure multi-graphe, voici le résultat obtenu :

En augmentant le coefficient de filtrage à 0.4 pour le mercure :

En noir et blanc, si on souhaite imprimer : il suffit de choisir un fond blanc et de ne pas faire appel au colormap pour la couleur des lignes :

Vous savez peut-être déjà qu’en lançant un code Python pour tracer un graphique, la fenêtre graphique peut être zoomée grâce à l’icône ‘loupe’ de la barre d’outils :

On peut alors zoomer sur un doublet, par exemple, ou aller chercher une valeur précise de longueur d’onde :

Le doublet du sodium

La fenêtre graphique affiche automatiquement les coordonnées du curseur de la souris, dans le repère du graphique. On peut afficher la longueur d’onde en survolant la raie avec le curseur :

Tout ceci se prête merveilleusement à une projection en classe, pour comparer les spectres et commenter sur une partie précise, en zoomant et dézoomant à volonté.


Les fichiers CSV utilisés pour faire ce document :

Le programme Python :


Pour les besoins d’un exercice, on peut tracer un spectre de raies fictif de longueurs d’onde déterminées par l’utilisateur. Je l’ai fait sur un autre programme.

import matplotlib.pyplot as plt
raies=[420, 532, 650]#longueurs d'onde souhaitées, en nm

#-----------------
"""Définition d'une fonction qui trace le spectre d'un élément.
element est le nom (chaîne de caractères, entre "")
listeLO est la liste de longueurs d'onde de cet élément.
"""
def trace(element, listeLO):
    for L in listeLO:
        plt.plot([L, L], [0, h], linewidth=ep, c=cmap((L-Lmin)/(Lmax-Lmin)))
#        plt.plot([L, L], [0, h], linewidth=ep, c='black')#raies en noir
        plt.ylabel(element, fontsize=12)
        plt.title(element, fontsize=12,pad=20)
        plt.yticks([])
        plt.xlim(Lmin, Lmax)#limites de l'axe x
        plt.ylim(0, h)
        if Lmax-L < 15:
            plt.text(L-20, 1.05*h, "%s" %(L))#affiche la longueur d'onde
        else : plt.text(L+4, 1.05*h, "%s" %(L))#affiche la longueur d'onde
#        plt.xticks(np.arange(400, 800, 100))#le dernier nombre est l'espacement des graduations en x


#Paramètres du graphique :----------------------------------
h=50#hauteur des raies (même échelle qu'en x)
ep=1#épaisseur des raies
couleurDeFond='black'# pour imprimer, mieux vaut 'white'
fig=plt.figure(figsize=(7, 2))

cmap=plt.get_cmap('turbo')#gamme de couleurs du visible
Lmin=350
Lmax=750

#GRAPHE -----------------------------------------------------
ax=plt.subplot(111, facecolor=couleurDeFond)
ax.set_aspect('equal')
trace("nom", raies)
plt.xlabel("longueur d'onde (nm)")

#Création d'un fichier image :
fig.savefig("SpectresDeRaiesdpi200.png", dpi=200)
plt.show()

Pour obtenir des spectres de raies d’absorption :

Dans ce programme, je trace un grand nombre de lignes colorées, de 350 à 750 nm, suivies des raies noires.

L’idée est que trouver le bon rapport entre l’épaisseur et le nombre de lignes colorées, afin que l’aspect visuel ait l’apparence de continuité.

À noter que, dans ce cas, un zoom important de la fenêtre graphique fera fatalement apparaître lesdites lignes colorées :

Soyez le premier à commenter

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.