View  Edit  Attributes  History  Attach  Print  Search

PYQTLAB

PyQt Lab' : Graphiques Math : Pyqtgraph : Afficher la courbe du corps noir et la paramétrer à l'aide d'un slider dans un graphique Pyqtgraph.

Par X. HINAULT - Juin 2013

Ce que l'on va faire ici

  • Afficher la courbe du corps noir et la paramétrer à l'aide d'un slider dans un graphique pyqtgraph.

Pré-requis

  • python 2.7
  • pyqt4.x
  • pyqtgraph
  • numpy
  • scipy

Infos utiles

  • La courbe du corps noir relie l'intensité lumineuse spectrale d'un corps chauffé.
  • Cette loi a été découverte par Planck et a résolu la fameuse "catastrophe ultra-violette", mettant en évidence la notion de "quantum d'énergie", ouvrant ainsi la voie à la relativité restreinte et à la physique quantique.
  • La formule est assez complexe mettant en jeu notamment la constante de Planck, la vitesse de la lumière et la constante Boltzmann :
  • Ce code est facilité grâce à l'utilisation de la librairie Scipy qui intégre les constantes physiques principales.

Le fichier d'interface *.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>609</width>
    <height>477</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>PyQt + pyqtgraph : Courbe et Sliders : loi corps noir</string>
  </property>
  <widget class="QSlider" name="horizontalSlider">
   <property name="geometry">
    <rect>
     <x>5</x>
     <y>415</y>
     <width>596</width>
     <height>19</height>
    </rect>
   </property>
   <property name="minimum">
    <number>1</number>
   </property>
   <property name="maximum">
    <number>10000</number>
   </property>
   <property name="singleStep">
    <number>100</number>
   </property>
   <property name="pageStep">
    <number>1000</number>
   </property>
   <property name="value">
    <number>5000</number>
   </property>
   <property name="orientation">
    <enum>Qt::Horizontal</enum>
   </property>
   <property name="tickPosition">
    <enum>QSlider::NoTicks</enum>
   </property>
   <property name="tickInterval">
    <number>1</number>
   </property>
  </widget>
  <widget class="QLabel" name="label_T">
   <property name="geometry">
    <rect>
     <x>230</x>
     <y>450</y>
     <width>51</width>
     <height>13</height>
    </rect>
   </property>
   <property name="text">
    <string>Temp =</string>
   </property>
  </widget>
  <widget class="QLCDNumber" name="lcdNumber_w">
   <property name="geometry">
    <rect>
     <x>285</x>
     <y>445</y>
     <width>64</width>
     <height>23</height>
    </rect>
   </property>
   <property name="styleSheet">
    <string notr="true">background-color: rgb(170, 255, 255);
color: rgb(0, 0, 127);</string>
   </property>
   <property name="segmentStyle">
    <enum>QLCDNumber::Flat</enum>
   </property>
   <property name="intValue" stdset="0">
    <number>5000</number>
   </property>
  </widget>
  <widget class="PlotWidget" name="plotwidget">
   <property name="geometry">
    <rect>
     <x>5</x>
     <y>10</y>
     <width>600</width>
     <height>400</height>
    </rect>
   </property>
  </widget>
  <widget class="QLabel" name="label_T_units">
   <property name="geometry">
    <rect>
     <x>355</x>
     <y>450</y>
     <width>51</width>
     <height>13</height>
    </rect>
   </property>
   <property name="text">
    <string>Kelvins</string>
   </property>
  </widget>
 </widget>
 <customwidgets>
  <customwidget>
   <class>PlotWidget</class>
   <extends>QGraphicsView</extends>
   <header>pyqtgraph</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections>
  <connection>
   <sender>horizontalSlider</sender>
   <signal>valueChanged(int)</signal>
   <receiver>lcdNumber_w</receiver>
   <slot>display(int)</slot>
   <hints>
    <hint type="sourcelabel">
     <x>181</x>
     <y>433</y>
    </hint>
    <hint type="destinationlabel">
     <x>309</x>
     <y>460</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>

 

Le fichier d'interface *.py

  • Fichier obtenu automatiquement avec l'utilitaire pyuic4 à partir du fichier *.ui créé avec QtDesigner :

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file
#
# Created: Fri Sep  6 15:34:14 2013
#      by: PyQt4 UI code generator 4.9.1
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName(_fromUtf8("Form"))
        Form.resize(609, 477)
        self.horizontalSlider = QtGui.QSlider(Form)
        self.horizontalSlider.setGeometry(QtCore.QRect(5, 415, 596, 19))
        self.horizontalSlider.setMinimum(1)
        self.horizontalSlider.setMaximum(10000)
        self.horizontalSlider.setSingleStep(100)
        self.horizontalSlider.setPageStep(1000)
        self.horizontalSlider.setProperty("value", 5000)
        self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
        self.horizontalSlider.setTickPosition(QtGui.QSlider.NoTicks)
        self.horizontalSlider.setTickInterval(1)
        self.horizontalSlider.setObjectName(_fromUtf8("horizontalSlider"))
        self.label_T = QtGui.QLabel(Form)
        self.label_T.setGeometry(QtCore.QRect(230, 450, 51, 13))
        self.label_T.setObjectName(_fromUtf8("label_T"))
        self.lcdNumber_w = QtGui.QLCDNumber(Form)
        self.lcdNumber_w.setGeometry(QtCore.QRect(285, 445, 64, 23))
        self.lcdNumber_w.setStyleSheet(_fromUtf8("background-color: rgb(170, 255, 255);\n"
"color: rgb(0, 0, 127);"))
        self.lcdNumber_w.setSegmentStyle(QtGui.QLCDNumber.Flat)
        self.lcdNumber_w.setProperty("intValue", 5000)
        self.lcdNumber_w.setObjectName(_fromUtf8("lcdNumber_w"))
        self.plotwidget = PlotWidget(Form)
        self.plotwidget.setGeometry(QtCore.QRect(5, 10, 600, 400))
        self.plotwidget.setObjectName(_fromUtf8("plotwidget"))
        self.label_T_units = QtGui.QLabel(Form)
        self.label_T_units.setGeometry(QtCore.QRect(355, 450, 51, 13))
        self.label_T_units.setObjectName(_fromUtf8("label_T_units"))

        self.retranslateUi(Form)
        QtCore.QObject.connect(self.horizontalSlider, QtCore.SIGNAL(_fromUtf8("valueChanged(int)")), self.lcdNumber_w.display)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQt + pyqtgraph : Courbe et Sliders : loi corps noir", None, QtGui.QApplication.UnicodeUTF8))
        self.label_T.setText(QtGui.QApplication.translate("Form", "Temp =", None, QtGui.QApplication.UnicodeUTF8))
        self.label_T_units.setText(QtGui.QApplication.translate("Form", "Kelvins", None, QtGui.QApplication.UnicodeUTF8))

from pyqtgraph import PlotWidget

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    Form = QtGui.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())




 

Le fichier d'application *Main.py


#!/usr/bin/python
# -*- coding: utf-8 -*-

# Par X. HINAULT - Tous droits réservés - GPLv3
# Déc 2012 - www.mon-club-elec.fr

# --- importation des modules utiles ---
from PyQt4.QtGui import *
from PyQt4.QtCore import * # inclut Qtimer..

import os,sys

import pyqtgraph as pg # pour accès à certaines constantes pyqtgraph, widget, etc...
# pas indisp. sinon car pyqtgraph est inclut par la déclaration QtDesigner de PlotWidget

import numpy

from scipy import e
from scipy.constants import codata

# --- importation du fichier de description GUI ---
from tuto_pyqt_pyqtgraph_courbe_corps_noir_slider import *

# classe principale contenant le code actif
class myApp(QWidget, Ui_Form): # la classe reçoit le Qwidget principal ET la classe définie dans test.py obtenu avec pyuic4

        # Note : ici self représente la classe

        def __init__(self, parent=None):
                QWidget.__init__(self) # initialise le Qwidget principal
                self.setupUi(parent) # Obligatoire


                #-- variables utiles --
                #-- variables utiles --
                self.c=codata.value('speed of light in vacuum') # constante vitesse de la lumière
                print ("Constante vitesse de la lumière dans le vide = "+ str(self.c))

                self.h=codata.value('Planck constant') # constante vitesse de la lumière
                print ("Constante de Planck = "+ str(self.h))

                self.kb=codata.value('Boltzmann constant') # constante de Boltzman
                print ("Constante de Boltzmann = "+ str(self.kb))

                self.T=5000.0 # température en kelvin

                #Ici, personnalisez vos widgets si nécessaire en utilisant les noms définis dans QtDesigner

                #Réalisez les connexions supplémentaires entre signaux et slots
                # les widgets sont désignés sous la forme self.nom en utilisant les noms définis dans QtDesigner
                self.connect(self.horizontalSlider, SIGNAL("valueChanged(int)"), self.horizontalSliderValueChanged)
                # connecte le signal valueChanged de l'objet Slider à l'appel de la fonction voulue

                # ------ code actif initial ------

                #-- initialise données --
                self.x = numpy.arange(0.1, 3000, 1) # plus précision réduite, plus rapide...
                numpy.seterr(over='ignore') # ignore warning overflow float
                print(self.x.dtype)
                print(self.x) # debug - affiche les valeurs x

                # calcul Y = la loi du corps noir
                self.y=(2*self.h*self.c**2/(self.x*1e-9)**5) *(pow(e,self.h*self.c/(self.x*1e-9*self.kb*self.T))-1)**-1

                # debug
                print(self.y) # debug - affiche les valeurs y
                print("max="+str(numpy.amax(self.y))) # extrait la valeur maxi

                index=numpy.argmax(self.y) # indice du max...
                print("indice max="+str(index)) # affiche l'index valeur maxi
                print("longueur onde max="+str(self.x[index])+ "nm") # extrait la long onde du pic maxi

                #-- initialise la courbe --
                self.plotwidget.hideAxis('left') # masque axes - ‘left’, ‘bottom’, ‘right’, or ‘top’
                self.plotwidget.setBackgroundBrush(QBrush(QColor(Qt.white))) # la classe PlotWidget est un GraphicsWidget qui est un QGraphics View
                self.plotwidget.showGrid(x=True, y=True)  # affiche la grille
                #self.plotwidget.setInteractive(False) # fonction QGraphics View : pour inactiver interaction souris
                self.plotwidget.getViewBox().setMouseMode(pg.ViewBox.RectMode)  # fonction ViewBox pas accessible depuis PlotWidget : fixe selection par zone

                self.plot1=self.plotwidget.plot(self.x,self.y, pen=(0,0,255)) # avec couleur - mémorise la courbe

                # --------------------- ligne violet 400nm  --------------------------
                self.vLine400 = pg.InfiniteLine(angle=90, movable=False) # crée une ligne inifinie
                self.vLine400.setPen(pg.mkPen(255,0,255)) # couleur de la ligne
                self.plotwidget.addItem(self.vLine400, ignoreBounds=True) # ajoute la ligne au graphique - fonction ViewBox accessible depuis PlotItem
                self.vLine400.setPos(400)
                # -------------------------- ligne rouge 700 nm --------------------------
                self.vLine700 = pg.InfiniteLine(angle=90, movable=False) # crée une ligne inifinie
                self.vLine700.setPen(pg.mkPen(255,0,0)) # couleur de la ligne
                self.plotwidget.addItem(self.vLine700, ignoreBounds=True) # ajoute la ligne au graphique - fonction ViewBox accessible depuis PlotItem
                self.vLine700.setPos(700)

                # -------------------------- ligne IRA - 1400nm --------------------------
                self.vLine1400 = pg.InfiniteLine(angle=90, movable=False) # crée une ligne inifinie
                self.vLine1400.setPen(pg.mkPen(127,0,0)) # couleur de la ligne
                self.plotwidget.addItem(self.vLine1400, ignoreBounds=True) # ajoute la ligne au graphique - fonction ViewBox accessible depuis PlotItem
                self.vLine1400.setPos(1400)

                # -------------------------- ligne IRB - 3000nm --------------------------
                self.vLine3000 = pg.InfiniteLine(angle=90, movable=False) # crée une ligne inifinie
                self.vLine3000.setPen(pg.mkPen(64,0,0)) # couleur de la ligne
                self.plotwidget.addItem(self.vLine3000, ignoreBounds=True) # ajoute la ligne au graphique - fonction ViewBox accessible depuis PlotItem
                self.vLine3000.setPos(3000)


        # les fonctions appelées, utilisées par les signaux
        def horizontalSliderValueChanged(self, valeur): # fonction appelée si changement valeur slider - reçoit la valeur courante
                print("Slider H modifié : valeur = " + str(valeur))

                self.T=valeur
                print self.T

                self.y=(2*self.h*self.c**2/(self.x*1e-9)**5) *(pow(e,self.h*self.c/(self.x*1e-9*self.kb*self.T))-1)**-1
                #self.y=(pow(self.x,3)/pow(e,self.x)-1)

                print(self.y) # debug - affiche les valeurs y
                print("max="+str(numpy.amax(self.y))) # extrait la valeur maxi

                index=numpy.argmax(self.y) # indice du max...
                print("indice max="+str(index)) # affiche l'index valeur maxi
                print("longueur onde max="+str(self.x[index])+ "nm") # extrait la long onde du pic maxi

                self.plot1.setData(self.x,self.y) # met à jour les données




        #--- fonctions actives
        # ...


# fonction principale exécutant l'application Qt                       
def main(args):
        a=QApplication(args) # crée l'objet application
        f=QWidget() # crée le QWidget racine
        c=myApp(f) # appelle la classe contenant le code de l'application
        f.show() # affiche la fenêtre QWidget
        r=a.exec_() # lance l'exécution de l'application
        return r

# pour rendre le fichier *.py exécutable
if __name__=="__main__": # pour rendre le code exécutable
        main(sys.argv) # appelle la fonction main

 

Utilisation

  • Les 2 fichiers suivants sont à enregistrer dans un même répertoire, l'un en nom.py et l'autre en nomMain.py.
  • Puis lancer l'application depuis Geany ou équivalent, en exécutant le fichier nomMain.py
  • Faire défiler le slider entraîne la modification de la courbe.