IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

PyQt Python Discussion :

Thread et pyqtgraph


Sujet :

PyQt Python

  1. #1
    Membre régulier
    Homme Profil pro
    Ingénieur développement de composants
    Inscrit en
    Décembre 2019
    Messages
    113
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement de composants

    Informations forums :
    Inscription : Décembre 2019
    Messages : 113
    Points : 72
    Points
    72
    Par défaut Thread et pyqtgraph
    Bonjour,
    j'utilise un thread pour mettre à jour de la data sur un graphique pyqtgraph.
    bon, le code ci dessous "tourne"... mais il y a parfois des warning qui s'affichent (par rapport à des courbes contenant des np.nan... et ce malgré l'utilisation de np.warnings.filterwarnings('ignore')).
    Aussi, il y a parfois pas mal de lag (voire des non mises à jour de certains graph ou label de l'ihm).

    je pense que le pyqtgraph implique, peut être, par lui même quelques thread qui ne font pas bon ménage avec le mien....
    Du coup, je me pose des question sur la "meilleure" manière de gérer les emit()... avant ou sans paramètre? une fois dans la fonction connectée à l'emit, est-il bon de faire référence à des variables du thread comme c'est le cas ci-dessous?)

    merci pour votre aide :-)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    class MainWindow(QMainWindow):
        def __init__(self):
            QMainWindow.__init__(self)
     
    	init_variables(self)
    	instances_widget(self) #les 3 lignes ci-dessous sont contenues dans cet appel)
    		instance_main.thread_simulation = update_datas_live_simulation(instance_main)
    		instance_main.thread_simulation.finished.connect(instance_main.terminate_thread_simulation)
    		instance_main.thread_simulation.signal_update_simulation.connect(instance_main.data_from_simulation)
    	connect_specif_fonctions(self)
    	construction_fenetre(self)
     
        def data_from_simulation(self):
            try:
                self.champ_texte_infos.setText(self.thread_simulation_passed.infos)
     
                self.graphique_price.setData(self.thread_simulation.x, self.thread_simulation.y_1)
                self.graphique_achat.setData(self.thread_simulation.x, self.thread_simulation.y_2)
                self.graphique_vente.setData(self.thread_simulation.x, self.thread_simulation.y_3)
     
            except Exception as e:
                print(e)
     
        def terminate_thread_simulation(self):
            print("Fin du thread")
     
    if __name__ == "__main__":
        appli = QApplication(sys.argv)
        fenetre_main = MainWindow()
        fenetre_main.show()
        sys.exit(appli.exec_())
     
     
     
    class update_datas_live_simulation(QThread):
     
        signal_update_simulation = pyqtSignal()
     
        def __init__(self, ui):
            QThread.__init__(self)
    	print("Init Thread")
            self.ui = ui
            self.init_variables()
     
        def run(self):
            print("Début Thread")
    	#....
    	#....
            x = x1
            y_1 = y_1b
            y_2 = y_2b
            y_3 = y_3b 
    	self.signal_update_simulation.emit()

  2. #2
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 690
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 690
    Points : 30 985
    Points
    30 985
    Billets dans le blog
    1
    Par défaut
    Bonjour

    Déjà quelques détails: la syntaxe parent.methode(self, autres_arguments_éventuels) est périmée et doit être remplacée par super().methode(argumnts_éventuels). Dans ton code, c'est QMainWindow.__init__(self) qui doit devenir super().__init__() et pareil pour QThread.__init__(self) qui devient super().__init__(). Un exemple plus complet ici.
    Ce n'est pas interdit de rester en obsolète mais c'est dommage. Si demain le parent change et passe de QMainWindow à autre chose, tu dois revoir tout ton code où tu l'utilises. Tandis qu'avec super() le changement est transparent.

    Ensuite pour simplifier, je ne pense pas que les éventuels thread de pyqtGraph (s'il y a) entrent en collision avec les tiens (les thread sont justement faits pour ça). Pour moi, ton souci ressemble à un souci d'accès concurrent entre le thread et ton pyqtGraph. Or généralement un souci d'accès concurrent se gère par l'utilisation de sémaphores. Typiquement ce serait un sémaphore libéré par pyqtGraph qui peut alors être pris par un thread pour le mettre à jour et ainsi de suite.

    Concernant ta question via pyqtSignal, je serais pour l'utilisation de paramètres portés par le signal permettant d'aller mettre à jour le graphique et non pas le graphique qui va récupérer directement les valeurs dans le thread. J'ai l'intuition que ce sera plus propre.

    Et pour le NaN je serais pour trouver leur origine et l'éliminer plutôt que les shunter à réception. C'est jamais bon de masquer un souci.

    Accessoirement si on avait un code assez complet permettant de tester chez-nous...
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  3. #3
    Membre régulier
    Homme Profil pro
    Ingénieur développement de composants
    Inscrit en
    Décembre 2019
    Messages
    113
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement de composants

    Informations forums :
    Inscription : Décembre 2019
    Messages : 113
    Points : 72
    Points
    72
    Par défaut
    Bonjour,
    merci pour votre réponse et vos conseils (sur la syntaxe), j'ai du coup inclus les appels relatifs aux "super()".
    ci dessous un bout de code pour illustrer concrètement ma situation.

    en ré-écrivant cet example (le code initial est décomposé en plusieurs fichiers *.py pour gagner en lisibilité), différentes choses ont changé dans l'interaction avec l'interface (c'est donc une bonne chose d'avoir pris le temps de ré-écrire quelque chose de testable niveau "architecture"):

    1 - Les warning se trouve être arrangés... il ne reste plus qu'un warning systématique répondant aux infos suivantes:
    RuntimeWarning: All-NaN slice encountered self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072).
    il doit donc y avoir une petite maladresse dans le "vrai code" => je vais vérifier l'imbrication globale!

    2 - Le quit() connecté au bouton stop ne fait plus le job... bon ça doit être une particularité de la boucle de signal qu'il y a dans le run... (je cherche!)

    Je ne sais pas trop quelle approche utiliser pour émettre les signaux sachant que le emit() n'accepte pas les arguments...
    Faire appel aux variables du thread à partir de la fonction connectée au signal? ou faire appel à des variables instanciées dans le main dans mon thread?
    les deux méthodologies ci dessus marchent mais je ne sais pas laquelle est à préférer...
    ou une autre approche? (je ne vois pas à quoi ressemblerait des "paramètres portés par le signal permettant d'aller mettre à jour le graphique")


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    from PyQt5.QtCore import QThread, pyqtSignal, QSize
    from PyQt5.QtWidgets import QPushButton, QWidget, QMainWindow, QApplication, QVBoxLayout, QHBoxLayout
    from PyQt5.QtGui import QPixmap, QTextCursor, QColor, QIcon
    import pyqtgraph as pg
    import pandas as pd
    import numpy as np
    import sys
    import random
    import time
     
    class update_datas_live_simulation(QThread):
     
        signal_update_simulation = pyqtSignal()
     
        def __init__(self, ui):
            super().__init__()
            print("Thread initialisé")
            self.ui = ui
     
        def run(self):
            print("Thread lancé...")
     
            self.x = []
            self.y_1 = []
            self.y_2 = []
            self.y_3 = []
     
            self.calcul()
     
        def calcul(self):
            for i in range(0, 50000, 1):
                time.sleep(0.01)
                self.data = random.random()*10
                self.x.append(i)
                self.y_1.append(self.data)
                if self.data > 9:
                    self.y_2.append(self.data)
                    self.y_3.append(np.nan)
                elif self.data < 1:
                    self.y_2.append(np.nan)
                    self.y_3.append(self.data)
                else:
                    self.y_2.append(np.nan)
                    self.y_3.append(np.nan)
                self.signal_update_simulation.emit()
     
        def stop(self):
            self.quit()
            print("Thread stoppé")
     
    class Bouton_simple(QPushButton):
        def __init__(self, ui, longueur, hauteur, texte, taille_texte, icone):
            QPushButton.__init__(self)
            self.setStyleSheet("""QPushButton{font: """ + str(taille_texte) + """px}""")
            if longueur!=None:
                self.setFixedWidth(longueur)
            if hauteur != None:
                self.setFixedHeight(hauteur)
            self.setText(texte)
            if not icone == None:
                self.setIcon(QIcon(icone[1]))
                self.setIconSize(QSize(20, 20))
     
    class MainWindow(QMainWindow):
        def __init__(self):
            super().__init__()
     
            #np.warnings.filterwarnings('ignore')
            self.init_variables()
            self.instances_widgets()
            self.connect_widgets_to_functions()
            self.construction_fenetre()
     
        def init_variables(self):
            pass
     
        def instances_widgets(self):
     
            self.fenetre_widget = QWidget()
     
            self.bouton_go = Bouton_simple(self, longueur=None, hauteur=None, texte="GO", taille_texte=12, icone = None)
            self.bouton_stop = Bouton_simple(self, longueur=None, hauteur=None, texte="STOP", taille_texte=12, icone = None)
     
            self.graphique_live = pg.GraphicsWindow()
            self.plot1 = self.graphique_live.addPlot(0, 0, title = "Courbe de test")
            self.plot1.addLegend()
            self.graphique_1 = self.plot1.plot([], [], pen=pg.mkPen('w', width=1))
            self.graphique_2 = self.plot1.plot([], [], pen=None, symbol='o', symbolBrush =(0, 255, 0), symbolSize = 14, symbolPen ='w', connect="finite")
            self.graphique_3 = self.plot1.plot([], [], pen=None, symbol='o', symbolBrush =(255, 0, 0), symbolSize = 14, symbolPen ='w', connect="finite")
     
            self.thread_simulation = update_datas_live_simulation(self)
            self.thread_simulation.finished.connect(self.terminate_thread_simulation)
            self.thread_simulation.signal_update_simulation.connect(self.data_from_simulation)
     
        def connect_widgets_to_functions(self):
            self.bouton_go.clicked.connect(lambda: self.start_thread_simulation())
            self.bouton_stop.clicked.connect(lambda: self.stop())
     
     
        def construction_fenetre(self):
     
            self.layout_horizontal_global = QHBoxLayout()
     
            self.layout_vertical_1 = QVBoxLayout()
            self.layout_vertical_2 = QVBoxLayout()
     
            self.layout_horizontal_global.addLayout(self.layout_vertical_1)
            self.layout_horizontal_global.addLayout(self.layout_vertical_2)
     
            self.layout_vertical_1.addWidget(self.graphique_live)
            self.layout_vertical_2.addWidget(self.bouton_go)
            self.layout_vertical_2.addWidget(self.bouton_stop)
     
            self.fenetre_widget.setLayout(self.layout_horizontal_global)
            self.setCentralWidget(self.fenetre_widget)
     
     
        def start_thread_simulation(self):
            print("Thread Start!")
            try:
                self.thread_simulation.start()
            except Exception as e:
                print(e)
     
        def data_from_simulation(self):
            try:
                self.graphique_1.setData(self.thread_simulation.x, self.thread_simulation.y_1)
                self.graphique_2.setData(self.thread_simulation.x, self.thread_simulation.y_2)
                self.graphique_3.setData(self.thread_simulation.x, self.thread_simulation.y_3)
            except Exception as e:
                print(e)
     
        def terminate_thread_simulation(self):
            print("Fin du thread")
     
        def stop(self):
            print("Demande de stopper le thread")
            if self.thread_simulation.isRunning():
                self.thread_simulation.stop()
            else:
                print("No")
     
    if __name__ == "__main__":
        appli = QApplication(sys.argv)
        fenetre_main = MainWindow()
        fenetre_main.show()
        sys.exit(appli.exec_())

    merci :-)

  4. #4
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 690
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 690
    Points : 30 985
    Points
    30 985
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par clement_74 Voir le message
    merci pour votre réponse et vos conseils (sur la syntaxe), j'ai du coup inclus les appels relatifs aux "super()".
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class Bouton_simple(QPushButton):
        def __init__(self, ui, longueur, hauteur, texte, taille_texte, icone):
            QPushButton.__init__(self)
    Visiblement pas !!!
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class Bouton_simple(QPushButton):
        def __init__(self, ui, longueur, hauteur, texte, taille_texte, icone):
            super().__init__()

    Citation Envoyé par clement_74 Voir le message
    2 - Le quit() connecté au bouton stop ne fait plus le job... bon ça doit être une particularité de la boucle de signal qu'il y a dans le run... (je cherche!)
    Remplace self.quit() par self.terminate().
    Accessoirement tu as parfaitement le droit de surcharger les fonctions officielles
    Exemple: la méthode self.stop() du thread devient
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    def terminate(self):
    	print("Thread stoppé")
    	super().terminate()
    Et bien évidemment dans ta méthode stop() du QMainWindow() tu remplaces self.thread_simulation.stop() par self.thread_simulation.terminate().

    Citation Envoyé par clement_74 Voir le message
    if not icone == None
    T'as pas trouvé plus compliqué? if icone != None !!!.
    Toutefois on ne compare jamais None car d'une part on peut très bien rendre ce test toujours vrai ou faux en surchargeant les opérateurs de comparaison...
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    >>> class toto:
    …	def __eq__(self, x): return Truedef __ne__(self, x): return False
    …
    >>> toto() == None
    True
    >>> toto() != None
    False
    ... et d'autre part, None ce n'est pas une valeur c'est un êtat => if icone is not None (et l'opérateur "is" est le seul opérateur qui ne puisse pas être surchargé).

    Citation Envoyé par clement_74 Voir le message
    sachant que le emit() n'accepte pas les arguments...

    https://pyqt.developpez.com/telechar...s-notre-signal

    Citation Envoyé par clement_74 Voir le message
    (je ne vois pas à quoi ressemblerait des "paramètres portés par le signal permettant d'aller mettre à jour le graphique")
    Ah ben quelque part il faudrait créer ton propre objet qui hériterait de GraphicsWindow pour que tu puisses lui rajouter des slots de ton cru...
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Tri multi-threadé
    Par Tifauv' dans le forum C
    Réponses: 8
    Dernier message: 28/06/2007, 09h00
  2. récupérer la valeur de sortie d'un thread
    Par jakouz dans le forum Langage
    Réponses: 3
    Dernier message: 31/07/2002, 11h28
  3. Programmer des threads
    Par haypo dans le forum C
    Réponses: 6
    Dernier message: 02/07/2002, 13h53
  4. Réponses: 5
    Dernier message: 12/06/2002, 15h12
  5. [Kylix] Pb de Thread !!
    Par Anonymous dans le forum EDI
    Réponses: 1
    Dernier message: 25/04/2002, 13h53

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo