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 |
…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_() |
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_() |
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_() |
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_()) |
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_()) |
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_()) |
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.
Ya pas a dire c’est beau.
J’ai lu le mot clef “javascript” je me suis rué ici, pour commenter, finalement ça en a valu le détour je me suis bien marré =P (prochaine étape faire ça avec tkinter? :o)
Super beau. Je ne connaissois point opencv mais j’aime bien.
Allez, petite optimisaiton possible:
(y, x) = divmod(index, largeur)
et surtoutcheckBox.setChecked(valeur > seuil)
. Ben oui, étant donné que la case à cocher n’est pas triState (ou SchrödingerState) on a alors le droit de raccourcir et passer par setChecked() au lieu de setCheckState().Ceci dit, si on veut vraiment passer par setCheckState(), alors le puriste préfèrera utiliser Qt.UnChecked et Qt.Checked au lieu de ces nombres magiques 0 et 2.
Bon allez, je vais convertir le film “Autant en emporte le vent” en cases à cocher…;)
Pour ceux qui, comme moi, ne parlent pas anglais: Ici la traduction française de l’article “You’re doing it wrong” ;)
C’est bien mais moi je voulais un poney !
ptite typo : fil-s de Stack Overflow, au singulier, sinon ça fait une insulte chelou
@dineptus : voilà un commentaire simple et efficace. Merci. :-)
@boblinux : ça se tente… :-p
@Fred :
(y, x) = divmod(index, largeur)
… whaou, merci… j’ai l’impression d’avoir un troisième bras… :-DEt, effectivement,
setChecked
est exactement adapté à la situation et c’est ce que je fais dans la version JS, je suis juste tombé en premier sursetCheckState
et je n’ai pas cherché plus loin… (selon la fameuse règle : si ça marche, c’est que c’est pertinent :-) )Merci donc pour ces remarques.
Je vais modifier le post en conséquence d’ici quelques jours dans la mesure où cela simplifie à mon sens la lecture du script.
@B.Pivot : Ah Ah… merci… fixed…
Quelle modernité !
On se croirait en 1995 ;-)
Merci pour ce super tuto, je commence à coder avec pyqt depuis quelques mois et j’adore !!
Mais pourquoi pyqt4 et pas pyqt5? Une raison particulière?
Super !
ça m’intéresse beaucoup ce que tu as fait, mais allons plus loin :
Plutôt que d’obtenir des cases à cocher, comment faire pour obtenir un gif animé étant traité au préalable
par un logiciel comme GMIC :
On pourrait faire une espèce de dessin animé dégradé à partir de videos !
Très intéressant pour des scènes de jeux vidéos.
Je pense qu’il faille utiliser pyOpenCL.
Si ce n’est pas faisable via GMIC, peut etre avec le module pillow, ou par un script pour GIMP…
Pardon, mais tout ça va encore troubler les jeunes de France.
Comment expliquer que le cocher peut constituer lui-même le cheval alors qu’il est communément admis qu’il est sur le cheval (ou derrière, sur un siège en cuir sale).
Hein ? HEIN ?
@Louis : J’essaie de coller au mieux au concept d’Innovation promu par la #FrenchTech. À savoir exploiter des idées vieilles de 20 ans pour en faire quelque chose d’inutile. Je trouve que je m’en sors pas mal… :-p
@deuzair : C’est juste que PyQt5 n’est pas dans les dépôts par défaut sur Ubuntu 14.04 (en tout cas pour Python 2.7).
@buffalo974 : Un filtre vidéo, en somme. Ma foi, oui, pourquoi pas. Ce qui me trotte dans la tête en ce moment c’est de pondre moi-même des algos de datamoshing en Python. Si j’y arrive j’en ferai un article…
@dway : Vous êtes exclu du lycée pendant les 3 prochains jours. L’exclusion définitive vous pend au nez.
Ahhhh pyqt
J’en ai aussi chié un moment avant de réussir à faire quelque-chose d’utile. J’ai beaucoup aimé traduire la doc et les exemple obscures en C++ vers python.
Super article… C’est génial et couillu cette façon de se présenter — et c’est tellement plus vrai que la majorité des trucs qu’on voit sur le net, genre “c’est super simple, je vois pas pourquoi vous galérez. Moi ça m’a pris 15 secondes pour tout faire marcher du premier coup. Si vous n’y arrivez pas, c’est parce que vous êtes pas le quart de la moitié aussi intelligent que moi”.
La vraie classe.
J’adore le style littéraire :-)
“J’avais un peu peur de rendre le truc utile, alors je l’ai laissé en l’état” m’a tué :)
T’as bien fait de mettre un gif, la version en JS chie comme prévu ! Il manque 5 cases à la dernière ligne donc je suppose que c’est la grille qui n’a pas gardé la bonne forme.
J’aime particulièrement la version haute résolution avec les cases qui se chevauchent.
@kontre : [Ctrl][+]/[-] pour ajuster la police de charshackterre et hophop.
@e-jambon : Merci… :-)
Je me dis qu’être honnête c’est un moyen comme un autre de ne pas passer pour un con… :-p
De toutes façons, je voudrai jouer le gars qui touche sa bille que je me ferai démasquer en 2 commentaires…
@kontre : En fait, il n’y a pas de grille. Les
input
s’empilent tous seuls dans unediv
qui a une taille fixée en dur en fonction de la dimension souhaitée des cases (14×14).Mais, il n’est pas impossible que ton navigateur de permette pas de descendre la taille des checkbox jusqu’à cette valeur.
Où que tes préférences d’affichages effectuent un petit zoom-out…
Dans ce cas, la remarque de @d.j.a.y devrait résoudre le problème.
Ahh ahhh ! Mes yeux ! Mes yeux fondent !
Voici ma wish-list pour tes prochains créations :
un visualiseur de son avec des scrolls bar. (Le son est joué, les scroll-bar montent et descendent selon les fréquences sonores)
un jeu de Simon (https://fr.wikipedia.org/wiki/Simon_%28jeu%29) avec les diodes “Verr Num”, “Scroll Lock” et “Caps Lock” du clavier.
un visualiseur de flux cartographique (OpenStreetMap, GoogleMap, …) en Ascii-art / libcaca.
un émetteur morse avec les bips de l’ordinateur
une machine à beurrer les tartines avec une imprimante (mais je crois que Gaston Lagaffe avait déjà inventé un premier prototype à partir d’une machine à écrire).
@Recher : Que de bonnes idées ! :-)
Pour le visualiseur de son avec des scrolls bar, j’ai fait un truc dans le genre il n’y a pas longtemps.
Il s’agissait alors de faire des vagues avec des sliders dans Puredata (ce n’est pas du Python, donc, mais ça commence par la même lettre :-p )