PyQt Lab est un outil puissant qui permet de transposer l’exemple Processing Math Graphing2DEquation en PyQt et d’optimiser les calculs avec le module numexpr. Il offre une interface graphique intuitive et facile à utiliser pour les développeurs qui souhaitent créer des applications graphiques complexes. Grâce à PyQt Lab, les développeurs peuvent créer des applications graphiques complexes en un temps record et avec une précision et une efficacité maximales. Dans cet article, nous allons examiner en détail les fonctionnalités de PyQt Lab et comment elles peuvent être utilisées pour transposer l’exemple Processing Math Graphing2DEquation et optimiser les calculs avec le module numexpr.
PyQt Lab’ : Transposition en PyQt de l’exemple Processing Math Graphing2DEquation et optimisation des calculs avec module numexpr
Par X. HINAULT – Juin 2013



Ce que l’on va faire ici
- Dans ce code PyQt, je transpose un exemple Processing parlant qui montre comment il est assez simple de transposer en PyQt un code Processing. L’exemple de code Processing que je transpose ici est fournit avec le logiciel Processing (Examples > Math > Graphing2DEquation )
- Les raisons qui me poussent à tester un tel exemple sont multiples :
- j’ai utilisé Processing pendant un bon moment, et c’est tout naturellement que la comparaison s’impose pour moi…
- évaluer la transposition d’un calcul complexe basé sur une double boucle intriquée en Processing vers l’utilisation de tableau Numpy
- évaluer la vitesse comparative entre un code Processing et le code équivalent en PyQt
- Ici le calcul utilisé appliqué à tous les pixels en temps réel lors du mouvement de la souris est basé sur : sin(n*cos(r) + 5*theta)
- En terme de codage, le code Processing et le code Python ont des tailles comparables.
Résultat obtenu
Pour une image 320 x 240 pixels en niveau de gris :
- 34 ms / image (31 ms/image en mode compilé) avec Processing
- 27 ms / image avec PyQt + Numpy
- 14 ms / image avec PyQt + Numpy + Numexpr.. soit une vitesse x 2.5 par rapport à Processing. Donc Python fait très bien le job !
Pré-requis
- python 2.7
- pyqt4.x
- pyqtgraph
- modules :
- python-numpy : calculs et manipulation de tableaux numériques
- python-numexpr : accélération des calculs réalisés avec numpy
Pour info : le code Processing de départ
L’exemple de code Processing que je transpose ici est fournit avec le logiciel Processing (Examples > Math > Graphing2DEquation ) :
* Graphing 2D Equations
* by Daniel Shiffman.
*
* Graphics the following equation:
* sin(n*cos(r) + 5*theta)
* where n is a function of horizontal mouse location.
*/
void setup() {
size(200,200);
frameRate(30);
}
void draw() {
loadPixels();
float n = (mouseX * 10.0) / width;
float w = 16.0; // 2D space width
float h = 16.0; // 2D space height
float dx = w / width; // Increment x this amount per pixel
float dy = h / height; // Increment y this amount per pixel
float x = –w/2; // Start x at -1 * width / 2
for (int i = 0; i < width; i++) {
float y = –h/2; // Start y at -1 * height / 2
for (int j = 0; j < height; j++) {
float r = sqrt((x*x) + (y*y)); // Convert cartesian to polar
float theta = atan2(y,x); // Convert cartesian to polar
// Compute 2D polar coordinate function
float val = sin(n*cos(r) + 5 * theta); // Results in a value between -1 and 1
//float val = cos(r); // Another simple function
//float val = sin(theta); // Another simple function
// Map resulting vale to grayscale value
pixels[i+j*width] = color((val + 1.0) * 255.0/2.0); // Scale to between 0 and 255
y += dy; // Increment y
}
x += dx; // Increment x
}
updatePixels();
}
Le fichier d’interface *.py
- Fichier obtenu automatiquement avec l’utilitaire pyuic4 à partir du fichier *.ui créé avec QtDesigner :
# Form implementation generated from reading ui file
#
# Created: Fri Jun 14 22:11:51 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(333, 258)
Form.setMouseTracking(True)
self.labelImage = QtGui.QLabel(Form)
self.labelImage.setGeometry(QtCore.QRect(5, 10, 320, 240))
self.labelImage.setStyleSheet(_fromUtf8(« background-color: rgb(255, 255, 255); »))
self.labelImage.setText(_fromUtf8(« »))
self.labelImage.setObjectName(_fromUtf8(« labelImage »))
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate(« Form », « Pyqt : Image modifiée par MouseMove », None, QtGui.QApplication.UnicodeUTF8))
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
# -*- coding: utf-8 -*-
# par X. HINAULT – Mai 2013 – Tous droits réservés
# GPLv3 – www.mon-club-elec.fr
# adaptation exemple Processing Math : Graphing 2D Equation
« » »
* Version originale Processing :
*
* Graphing 2D Equations
* by Daniel Shiffman.
*
* Graphics the following equation:
* sin(n*cos(r) + 5*theta)
* where n is a function of horizontal mouse location.
« » »
# modules a importer
from PyQt4.QtGui import *
from PyQt4.QtCore import * # inclut QTimer..
import os,sys
import numpy as np
import numexpr as ne # accélère numpy : https://code.google.com/p/numexpr/ !
import time # temps
from tuto_pyqt_dessin_qpixmap_qimage_mouse_move_2D_equation import * # fichier obtenu à partir QtDesigner et pyuic4
# +/- variables et objets globaux
class myApp(QWidget, Ui_Form): # la classe reçoit le Qwidget principal ET la classe définie dans test.py obtenu avec pyuic4
def __init__(self, parent=None):
QWidget.__init__(self) # initialise le Qwidget principal
self.setupUi(parent) # Obligatoire
# — Variables de classe
self.width=self.labelImage.width()
self.height=self.labelImage.height()
self.n = (0.0* 10.0) / self.width #(mouseX * 10.0) / self.width;
self.w = 16.0 # 2D space width
self.h = 16.0 # 2D space height
self.dx = self.w / self.width # Increment x this amount per pixel
self.dy = self.h / self.height # Increment y this amount per pixel
# — Paramétrage des widgets de l’interface GUI si nécessaire —
# — Connexions entre signaux des widgets et fonctions
# connecte chaque signal utilisé des objets à l’appel de la fonction voulue
# — Code actif initial —
# Initialisation QImage
# tableau numpy définissant l’image
self.pixels=np.fromfunction(self.f,(self.labelImage.height(),self.labelImage.width())) # tableau 320×240 x 1 canal et 8U – pixels calculés par fonction
#QImage(self,str data, int width, int height, Format format)
self.image=QImage(self.pixels.tostring(), self.labelImage.width() , self.labelImage.height() ,QImage.Format_Indexed8) # crée image RGB 8 bits même taille que label
#– Affichage initial —
self.pixmap=QPixmap.fromImage(self.image) # initialise le QPixmap…
self.updatePixmap(10,10)
#—– initialisation capture évènement souris du Qlabel
self.labelImage.setMouseTracking(True) # active le suivi position souris
self.labelImage.installEventFilter(self) # défini un objet dans cette classe pour « filtrer » les évènements en provenance de l’objet label – méthode classe Object
# — les fonctions appelées, utilisées par les signaux des widgets —
# — les fonctions appelées, utilisées par les signaux hors widgets —
# fonction pour gérer filtrage évènements
#def eventFilter(self, _, event): # fonction pour gérer filtrage évènements
def eventFilter(self, srcEvent, event): # fonction pour gérer filtrage évènements – reçoit l’objet source de l’évènement
print (« event « )
#print srcEvent
if srcEvent==self.labelImage: # teste si la source de l’évènement est bien le label – utile si plusieurs sources d’évènements activées
if event.type() == QEvent.MouseMove: # si évènement « mouvement de la souris »
x=event.pos().x() # coordonnée souris au moment évènement dans le widget source de l’évènement =
y=event.pos().y()
print (« MouseMove : x= »+ str(x) + » y= « + str(y)) # affiche coordonnées
self.micros0=self.micros() # temps reference
self.updateImage(x,y) # met à jour le qimage
self.updatePixmap(x,y) # met à jour le pixmap
# affiche delai
deltaMicros=self.micros()–self.micros0 # calcul délai
print str(deltaMicros/1000.0)+ » ms »
# note : temps comparatif avec même nombre pixels : Processing = 34ms (31ms en mode compilé)soit 30 fps, PyQt = 27ms soit 37 fps
# encore mieux… en utilisant numexpr (sudo apt-get install numexpr) on fait encore mieux que numpy : 14ms soit 70 fps environ… !
# soit près de 2 fois mieux que numpy…
# et près de 2,5 fois mieux que Processing… tout en étant beaucoup plus light à lancer…
# de plus ici : usage CPU seulement si mouvement souris – tombe à 0% sinon… Avec Processing : usage CPU permanent car Framerate obligé
# pour la liste des QEvent : voir : http://pyqt.sourceforge.net/Docs/PyQt4/qevent.html
return False # obligatoire…
# — fonctions de classes autres—
def micros(self):
return(int(round(time.time() * 1000000))) # microsecondes de l’horloge système
def updateImage(self, xIn, yIn):
#print (« updateImage »)
self.n = (xIn* 10.0) / self.width #(mouseX * 10.0) / self.width;
# tableau numpy définissant l’image
self.pixels=np.fromfunction(self.f,(self.labelImage.height(),self.labelImage.width())) # tableau 320×240 x 1 canal et 8U – pixels calculés par fonction
#QImage(self,str data, int width, int height, Format format)
self.image=QImage(self.pixels.tostring(), self.labelImage.width() , self.labelImage.height() ,QImage.Format_Indexed8) # crée image RGB 8 bits même taille que label
#self.image.loadFromData(self.pixels.tostring()) # recharge à partir données # ne « marche pas »..
def updatePixmap(self, xIn, yIn):
# chargement du QImage dans le QPixmap
self.pixmap.convertFromImage(self.image) # recharge le QImage dans le QPixmap existant – met à jour le QPixmap
#– affichage du QPixmap dans QLabel
self.labelImage.setPixmap(self.pixmap) # met à jour le qpixmap affiché dans le qlabel
#– fonctions math utiliser pour calculer la valeur des pixels
def f(self, j, i): # Attention – inversion xIn et yIn : pour calcul pixel (x,y) la fonction reçoit (y,x) !
# ici i et j sont les indices des pixels – i : indice en abscisse de 0 à width-1 et j : indice en orodnnée de 0 à heigth-1
# ici x et y sont 2 grandeurs calculées à partir de i et j, les index des pixels d’abscisse i et d’ordonnée j
# au lieu de l’incrémentation dans une boucle, on calcule x et y à partir indice i,j…
x=-self.w/2+(i*self.dx)
y=-self.h/2+(j*self.dy)
n=self.n # pour usage local possible avec ne.evaluate()
# calcul à réaliser en utilisant les valeurs x,y calculées à partir de i,j
#r=np.sqrt((x*x) + (y*y)) # avec numpy
r = ne.evaluate(« sqrt((x*x) + (y*y)) ») # avec numpexpr
#theta=np.arctan2(y,x) # avec numpy
theta=ne.evaluate(« arctan2(y,x) »)
#valeur=np.sin(self.n*np.cos(r)+(5*theta)) # avec numpy
valeur=ne.evaluate(« sin(n*cos(r)+(5*theta)) ») # avec numpexpr
# valeur=(valeur+1)*255.0/2.0 # avec numpy
valeur=ne.evaluate(« (valeur+1)*255.0/2.0 »)
return np.uint8(valeur) # renvoie la valeur au format np.uint8
# — Autres Classes utiles —
# — Classe principale (lancement) —
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
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
- Lorsque l’on déplace le pointeur de souris sur le graphique, celui-ci se modifie en conséquence…
Articles similaires:
- PyQt Lab’ : Appliquer une opération mathématique f(x,y) à tous les pixels d’une image en niveaux de gris
- PyQt Lab’ : Appliquer une opération mathématique f(x,y) à tous les pixels d’une image couleur RGB
- PyQt Lab’ : Créer une image en niveaux de gris à l’aide d’un tableau numpy
- PyQt Lab’ : Créer une image couleur RGB à l’aide d’un tableau numpy
- PyQt Lab’ : Dessin : Dessiner un simple cercle par accès direct aux pixels
Articles Liés
- Javascript : Graphique Dygraphs simple
Le Javascript est un langage de programmation très populaire et puissant qui permet aux développeurs…
- Javascript : Afficher 6 widgets graphiques fournis par une librairie graphique externe.
Le Javascript est un langage de programmation très populaire qui permet aux développeurs de créer…
- Javascript : Graphique Dygraphs : afficher date à partir unixtime
Le langage de programmation Javascript est très populaire et est utilisé pour créer des applications…