Ceci est un post invité de 01ivier posté sous licence creative common 3.0 unported.

Bonsjours les amis,

J’espère que vous allez bien et que vous n’avez pas trop pensé que j’étais mort rapport au fait que ça faisait longtemps que je n’avais pas posté d’article.

Où je mentionne plusieurs fois le mot JavaScript.

Le fait est que j’étais trop occupé à faire du JavaScript.

Bon, ce n’est pas exactement vrai, mais, je voulais vérifier une théorie selon laquelle dès qu’on parle de JavaScript sur Sam&Max, le nombre des commentaires explosent (je n’ai pas de théorie sur le fait qu’il faille l’écrire en gras, mais je trouve ça joli (sauf quand il y en a partout (parce qu’après ça fait trop))).

Mais, dire que j’étais trop occupé à faire du JavaScript n’est pas précisément faux non plus dans la mesure où je me suis mis à p5.js, qui est au JavaScript ce que Processing est au Java, à savoir un environnement de développement simplifié pour les pucelles et autres puceaux de la programmation.

p5.js qui m’a permis d’assouvir un de mes fantasmes : afficher un cheval qui court avec des cases à cocher dans une page web..

Je vous ai mis un GIF animé, pour faire de l’animation, mais vous pouvez le voir en vrai ici avec vos cases à cocher à vous.

(Sauf pour celles et ceux pour qui ça ne marchera pas, bien évidemment.)

(Voilà pour la partie JavaScript. Je ne vais plus en parler. Les personnes concernées peuvent donc se rendre dès à présent à la section des commentaires. Merci.)

Où je m’attelle à une tâche bien plus sérieuse.

J’étais content, mais, passé ces enfantillages, il me fallait désormais m’atteler à une tâche bien plus sérieuse : afficher un cheval qui court avec des cases à cocher en Python.

Pour cela, j’ai choisi d’utiliser PyQt4 parce que c’est la meilleure librairie pour créer des interfaces graphiques en Python.

…

Ah Ah Ah…

Nan, mais, comme si j’en savais quelque chose…

C’est juste qu’un ami avait déjà réussi à afficher un bouton avec…

Donc, je me suis dit que c’était pour moi…

Bon, en fait, ce n’est pas pour moi…

Mais, que les choses soient claires, c’est l’informatique en général et la programmation en particulier qui ne sont pas pour moi…

Je veux dire, quand je vais lire Stack Overflow, j’ai de la peine pour moi…

C’est à dire que je me fais vraiment pitié tant je comprends que dalle…

Il m’est déjà arrivé de mettre une dizaine de secondes pour savoir si je lisais bien du Python (et s’en était)…

Nan, c’est moche…

Mais bon, je ne sais faire que de l’informatique (on me souffle à l’oreille que je sais aussi faire du Chirashi et c’est vrai)

Donc, je fais de l’informatique… (et du Chirashi)

Et j’en chie…

Mais à la fin je gagne…

Et je me paye même le luxe d’en faire un article sur S&M…

Pourquoi je vous raconte ça ?

Aucune idée…

Toujours est-il que j’ai choisi PyQt4…

Que ça n’est pas pour moi…

Mais que [SPOILER ALERT] ça a fini par marcher…

…

Où j’affiche une fenêtre. UNE FE-NÊ-TRE. Oui messieurs, dames.

D’abord, il faut installer PyQt4…

sudo apt-get install python-qt4 sudo apt-get install python-qt4

…et on ne peut pas dire que ce soit trop compliqué.

Et, voici le code minimal pour afficher une fenêtre :

#-*- coding: utf-8 -*- from PyQt4 import QtGui # On crée une appli Qt application = QtGui. QApplication ( [ ] ) # On fout quelque chose dedans fond = QtGui. QWidget ( ) # On l'affiche fond. show ( ) # On lance l'appli application. exec_ ( ) #-*- coding: utf-8 -*- from PyQt4 import QtGui # On crée une appli Qt application = QtGui.QApplication([]) # On fout quelque chose dedans fond = QtGui.QWidget() # On l'affiche fond.show() # On lance l'appli application.exec_()

Les connaisseurs apprécieront mon désormais célèbre style procédural de champion du monde.

Mais bon. Ça marche où ça marche pas ?

Ça marche.

Où j’affiche un bouton dans la fenêtre.

Toujours en procédural, voici comment on ajoute un bouton à notre fenêtre (que l’on customize un peu au passage).

(J’insiste pour le procédural car, comme PyQt4 est conçu pour être utilisé en Programmation Orientée Objet, vous ne verrez pas ça tous les jours)

#-*- coding: utf-8 -*- from PyQt4 import QtGui app = QtGui. QApplication ( [ ] ) # On crée un fond et lui donne une taille, une position sur l'écran et un titre fond = QtGui. QWidget ( ) fond. resize ( 300 , 200 ) fond. move ( 100 , 100 ) fond. setWindowTitle ( "C'est formidable" ) # On crée un bouton sur notre fond et on lui donne une position sur celui-ci bouton = QtGui. QPushButton ( 'Mon petit bouton' , fond ) bouton. move ( 80 , 80 ) fond. show ( ) app. exec_ ( ) #-*- coding: utf-8 -*- from PyQt4 import QtGui app = QtGui.QApplication([]) # On crée un fond et lui donne une taille, une position sur l'écran et un titre fond = QtGui.QWidget() fond.resize(300, 200) fond.move(100, 100) fond.setWindowTitle("C'est formidable") # On crée un bouton sur notre fond et on lui donne une position sur celui-ci bouton = QtGui.QPushButton('Mon petit bouton',fond) bouton.move(80, 80) fond.show() app.exec_()

Et hop…

La première fois que ça a marché, j’ai cliqué 25 fois sur le bouton et j’en garde un très bon souvenir.

Où j’affiche plein de cases à cocher dans la fenêtre.

Bon, on peut afficher tout un tas de bordel dans une fenêtre, vous vous en doutez. Des images, du texte, des champs texte, des sliders, des menus déroulants, des labels, des séparations…

Mais, ça n’est pas le propos ici.

Nous ce qu’on veut, ce sont des cases à cocher. Plein de cases à cocher.

J’ai commencé par les placer dans une grille, mais, je me suis rendu compte qu’en leur donnant des positions absolues en pixels, il était possible de les faire se toucher, voire se chevaucher. Ce qui, quand on veut afficher un cheval, apporte un petit plus indéniable.

(chevaucher/cheval/humour)

Allons y donc pour nos cases à cocher.

(Je vous recolle tout à chaque fois parce que je sais qu’il y a des goulus parmi vous.)

#-*- coding: utf-8 -*- from PyQt4 import QtGui # On définie les dimensions de notre bazar largeur = 40 hauteur = 30 tailleCase = 14 app = QtGui. QApplication ( [ ] ) fond = QtGui. QWidget ( ) fond. resize ( largeur*tailleCase+ 6 , hauteur*tailleCase+ 6 ) # le +6 c'est pour tenir compte des marges fond. setWindowTitle ( u "Plein de cases à cocher" ) # On parcourt les ordonnées for j in range ( hauteur ) : # On parcourt les abscisses for i in range ( largeur ) : # On crée une case à cocher sur notre fond check_box = QtGui. QCheckBox ( fond ) # Et on la positionne check_box. move ( i*tailleCase , j*tailleCase ) fond. show ( ) app. exec_ ( ) #-*- coding: utf-8 -*- from PyQt4 import QtGui # On définie les dimensions de notre bazar largeur = 40 hauteur = 30 tailleCase =14 app = QtGui.QApplication([]) fond = QtGui.QWidget() fond.resize(largeur*tailleCase+6, hauteur*tailleCase+6) # le +6 c'est pour tenir compte des marges fond.setWindowTitle(u"Plein de cases à cocher") # On parcourt les ordonnées for j in range(hauteur): # On parcourt les abscisses for i in range(largeur): # On crée une case à cocher sur notre fond check_box = QtGui.QCheckBox(fond) # Et on la positionne check_box.move(i*tailleCase, j*tailleCase) fond.show() app.exec_()

Où j’affiche plein de cases à cocher dans la fenêtre mais en utilisant cette fois la Programmation Orientée Objet.

On arrive au bout. Mais, ce qu’il faut comprendre avec pyQt c’est que quand le programme arrive à l’instruction app.exec_() il démarre une boucle. On est alors un peu coincé pour parler avec l’interface.

Il est possible de cliquer sur les cases à la main, soit, mais, nous ce qu’on veut, c’est que ce soit un bout de code qui le fasse à notre place (car ayant bien mis 2 minutes pour composer une seule image affichant le mot “Joie”, j’ai des doutes pour le 25 fps).

Dans tous les cas, il n’est plus possible de rester en procédural. Voici donc la version POO du précédent script :

#-*- coding: utf-8 -*- from PyQt4 import QtGui import sys largeur = 40 hauteur = 30 tailleCase = 14 class CheckBoxVideo ( QtGui. QWidget ) : # On initialise la class, j'imagine... def __init__ ( self ) : super ( CheckBoxVideo , self ) . __init__ ( ) self . interface ( ) # On initialise l'interface def interface ( self ) : self . resize ( largeur*tailleCase+ 6 , hauteur*tailleCase+ 6 ) self . setWindowTitle ( u "Plein de cases à cocher, mais en POO." ) for j in range ( hauteur ) : for i in range ( largeur ) : check_box = QtGui. QCheckBox ( self ) check_box. move ( i*tailleCase , j*tailleCase ) self . show ( ) if __name__ == "__main__" : appli = QtGui. QApplication ( sys . argv ) ckbx = CheckBoxVideo ( ) sys . exit ( appli. exec_ ( ) ) #-*- coding: utf-8 -*- from PyQt4 import QtGui import sys largeur = 40 hauteur = 30 tailleCase = 14 class CheckBoxVideo(QtGui.QWidget): # On initialise la class, j'imagine... def __init__(self): super(CheckBoxVideo, self).__init__() self.interface() # On initialise l'interface def interface(self): self.resize(largeur*tailleCase+6, hauteur*tailleCase+6) self.setWindowTitle(u"Plein de cases à cocher, mais en POO.") for j in range(hauteur): for i in range(largeur): check_box = QtGui.QCheckBox(self) check_box.move(i*tailleCase, j*tailleCase) self.show() if __name__ == "__main__": appli = QtGui.QApplication(sys.argv) ckbx = CheckBoxVideo() sys.exit(appli.exec_())

J’ai ajouté un petit if __name__ == "__main__" histoire de laisser croire que je comprends ce que je fais.

Et puis j’ai intégré la librairie sys pour être conforme avec tous les exemples de tous les tutos que j’ai trouvé. Mais c’est vraiment par respect pour les devs qui se sont donné du mal à les écrire. Parce que, vous pouvez essayer, ça marche très bien sans.

Bref.

Où, en faisant ce qu’il ne faut pas faire, je constate qu’il ne faut pas le faire.

Je vous épargnerai la liste des trucs ridicules que j’ai essayé pour entrer dans cette boucle et modifier l’état des cases à cocher et n’évoquerai que les solutions que j’ai trouvé pour faire marcher mon bazar… pendant 20 secondes.

En effet, à force de chercher, je suis tombé sur ce fil de Stack Overflow où la réponse la mieux notée donne trois moyens différents de créer des threads avec Qt.

J’ai méticuleusement tout recopié à l’arrache et testé chacune des solutions.

Toutes m’ont permis de voir ma silhouette en cases à cocher avant de me retourner un délicat segfault au bout de 20 secondes.

Vraisemblablement, il y a quelque chose que je faisais mal.

Chose qui a été confirmée par un article intitulé : “You’re doing it wrong…”

C’est à ce moment que j’ai décidé de me coucher, vu que le Soleil se levait.

Où j’ai la confirmation que dormir peut s’avérer parfois utile pour se reposer.

Si j’étais mort dans mon sommeil ce jour là, mes derniers mots auraient été : “Putain de boucle de merde”.

Mais je me suis réveillé et je me suis dis que, tout de même, il devait y avoir un moyen simple de faire exécuter du code dans cette boucle principale.

Et là, avec un cerveau frais, je me suis souvenu de cet exemple expliquant comment animer une barre de progression.

Passer des valeurs à un objet pour en changer son état, c’est un peu ce que je cherchais à faire, non ?

Non.

C’était exactement ce que je cherchais à faire.

Le principe est de créer un timer et d’écouter ses tours d’horloge pour déclencher des événements.

À noter la nécessité d’importer QtCore pour cela.

#-*- coding: utf-8 -*- from PyQt4 import QtGui , QtCore import sys largeurOut = 40 hauteurOut = 30 tailleCase = 14 class CheckBoxVideo ( QtGui. QWidget ) : def __init__ ( self ) : super ( CheckBoxVideo , self ) . __init__ ( ) self . interface ( ) def interface ( self ) : self . resize ( largeurOut*tailleCase+ 6 , hauteurOut*tailleCase+ 6 ) self . setWindowTitle ( "OMG ! Mais que se passe-t-il dans la console ?" ) for j in range ( hauteurOut ) : for i in range ( largeurOut ) : check_box = QtGui. QCheckBox ( self ) check_box. move ( i*tailleCase , j*tailleCase ) # On crée le timer. self . timer = QtCore. QBasicTimer ( ) # Et on le lance en renseignant un délais pour chaque tour d'horloge. # Ici, 40 millisecondes, pour tenter (naïvement) d'obtenir du 25 images par seconde. self . timer . start ( 40 , self ) self . show ( ) # Et voici la méthode qui écoute le timer. def timerEvent ( self , e ) : print ( "Ce message va s'écrire en console toutes les 40 ms." ) if __name__ == "__main__" : appli = QtGui. QApplication ( sys . argv ) ckbx = CheckBoxVideo ( ) sys . exit ( appli. exec_ ( ) ) #-*- coding: utf-8 -*- from PyQt4 import QtGui, QtCore import sys largeurOut = 40 hauteurOut = 30 tailleCase = 14 class CheckBoxVideo(QtGui.QWidget): def __init__(self): super(CheckBoxVideo, self).__init__() self.interface() def interface(self): self.resize(largeurOut*tailleCase+6, hauteurOut*tailleCase+6) self.setWindowTitle("OMG ! Mais que se passe-t-il dans la console ?") for j in range(hauteurOut): for i in range(largeurOut): check_box = QtGui.QCheckBox(self) check_box.move(i*tailleCase, j*tailleCase) # On crée le timer. self.timer = QtCore.QBasicTimer() # Et on le lance en renseignant un délais pour chaque tour d'horloge. # Ici, 40 millisecondes, pour tenter (naïvement) d'obtenir du 25 images par seconde. self.timer.start(40, self) self.show() # Et voici la méthode qui écoute le timer. def timerEvent(self, e): print("Ce message va s'écrire en console toutes les 40 ms.") if __name__ == "__main__": appli = QtGui.QApplication(sys.argv) ckbx = CheckBoxVideo() sys.exit(appli.exec_())

Où j’évoque ma jeunesse.

Il s’agissait maintenant de trouver un moyen de traiter du flux vidéo.

Mais comme je suis un ouf et que non seulement j’avais déjà réussi à afficher de la vidéo 3-bit dans mon terminal , mais qu’en plus je vous en avais fait part ici-même, et bien je suis allé copier/coller mon pâté. Tout simplement.

Pour celles et ceux qui n’ont pas eu la force psychologique de cliquer sur le lien, sachez que la solution que j’avais retenue à l’époque pour lire un fichier vidéo afin d’en traiter les données était OpenCV, et que c’est tout aussi pertinent maintenant.

(Enfin, ça marche. Ce qui, à mon niveau, est la définition même de la pertinence.)

Où il est de nouveau affiché un cheval qui court en cases à cocher, mais en Python cette fois.

Et voilà…

C’est terminé…

Voici le script après l’intégration du code exploitant OpenCV :

#-*- coding: utf-8 -*- from PyQt4 import QtGui , QtCore import sys import cv2 largeur = 50 hauteur = 34 tailleCase = 14 # On crée une liste qui va stocker les QCheckBox listeCB = [ ] # On définit un seuil pour pour cocher ou non les cases seuil = 150 # On définit le fichier à lire. video = cv2. VideoCapture ( 'cheval_qui_court.mjpg' ) class CheckBoxVideo ( QtGui. QWidget ) : def __init__ ( self ) : super ( CheckBoxVideo , self ) . __init__ ( ) self . interface ( ) def interface ( self ) : self . resize ( largeur*tailleCase+ 6 , hauteur*tailleCase+ 6 ) self . setWindowTitle ( u "Un cheval qui court en cases à cocher" ) for j in range ( hauteur ) : for i in range ( largeur ) : check_box = QtGui. QCheckBox ( self ) check_box. move ( i*tailleCase , j*tailleCase ) # On ajoute chaque QCheckBox dans notre liste listeCB. append ( check_box ) self . timer = QtCore. QBasicTimer ( ) self . timer . start ( 40 , self ) self . show ( ) def timerEvent ( self , e ) : # On lit une frame de la vidéo ret , img = video. read ( ) # On la passe en niveau de gris gray = cv2. cvtColor ( img , cv2. COLOR_BGR2GRAY ) # On la retaille pour que le nombre de pixel corresponde au nombre de cases à cocher gray = cv2. resize ( gray , ( largeur , hauteur ) ) # On parcourt notre liste for index , checkBox in enumerate ( listeCB ) : # On transforme les index en coordonnées x = index%largeur y = index/largeur # On récupère le niveau de gris du pixel concerné valeur = gray. item ( y , x ) # En fonction de la valeur on coche ou non la case à cocher if ( valeur > seuil ) : checkBox. setCheckState ( 0 ) else : checkBox. setCheckState ( 2 ) # Les plus attentifs auront remarqué que l'état de la case à cocher ne dépend pas d'un booléen. # En effet, il existe un état intermédiaire, le (1), que j'ai appelé l'état de Schrödinger, où # la case à cocher est partiellement cochée. Un peu comme si elle était à la fois cochée et non cochée. # (Et j'interdis à quiconque d'évoquer, qu'en fait, elle n'est ni l'une, ni l'autre.) if __name__ == "__main__" : appli = QtGui. QApplication ( sys . argv ) ckbx = CheckBoxVideo ( ) sys . exit ( appli. exec_ ( ) ) #-*- coding: utf-8 -*- from PyQt4 import QtGui, QtCore import sys import cv2 largeur = 50 hauteur = 34 tailleCase = 14 # On crée une liste qui va stocker les QCheckBox listeCB = [] # On définit un seuil pour pour cocher ou non les cases seuil = 150 # On définit le fichier à lire. video = cv2.VideoCapture('cheval_qui_court.mjpg') class CheckBoxVideo(QtGui.QWidget): def __init__(self): super(CheckBoxVideo, self).__init__() self.interface() def interface(self): self.resize(largeur*tailleCase+6, hauteur*tailleCase+6) self.setWindowTitle(u"Un cheval qui court en cases à cocher") for j in range(hauteur): for i in range(largeur): check_box = QtGui.QCheckBox(self) check_box.move(i*tailleCase, j*tailleCase) # On ajoute chaque QCheckBox dans notre liste listeCB.append(check_box) self.timer = QtCore.QBasicTimer() self.timer.start(40, self) self.show() def timerEvent(self, e): # On lit une frame de la vidéo ret,img = video.read() # On la passe en niveau de gris gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # On la retaille pour que le nombre de pixel corresponde au nombre de cases à cocher gray = cv2.resize(gray, (largeur, hauteur)) # On parcourt notre liste for index, checkBox in enumerate(listeCB): # On transforme les index en coordonnées x = index%largeur y = index/largeur # On récupère le niveau de gris du pixel concerné valeur = gray.item(y, x) # En fonction de la valeur on coche ou non la case à cocher if (valeur > seuil): checkBox.setCheckState(0) else: checkBox.setCheckState(2) # Les plus attentifs auront remarqué que l'état de la case à cocher ne dépend pas d'un booléen. # En effet, il existe un état intermédiaire, le (1), que j'ai appelé l'état de Schrödinger, où # la case à cocher est partiellement cochée. Un peu comme si elle était à la fois cochée et non cochée. # (Et j'interdis à quiconque d'évoquer, qu'en fait, elle n'est ni l'une, ni l'autre.) if __name__ == "__main__": appli = QtGui.QApplication(sys.argv) ckbx = CheckBoxVideo() sys.exit(appli.exec_())

Et voici le cheval qui court en cases à cocher :

Je concède qu’arrivé là, il n’aurait pas été totalement farfelu d’ajouter un slider pour gérer le niveau du seuil.

Mais j’avais un peu peur de rendre le truc utile, vous voyez.

Donc j’ai laissé en l’état.

Où je conclue.

Je pense qu’il est assez clair désormais qu’il est possible de sauver l’Humanité en programmant en Python.

Je vous encourage donc à le faire.

À bientôt ou tard.

Où j’ajoute tout de même un bonus.

Et voici un cheval qui court avec des cases à cocher qui se chevauchent.

Ne me remerciez pas, ça me fait plaisir.