PyQt5 mit dynamischem matplotlib

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
mushroom
User
Beiträge: 58
Registriert: Sonntag 21. November 2010, 12:32

Hallo zusammen,

ich möchte mit PyQt5 ein Fenster öffnen, welches Daten plotten und später Messungen durchführen soll. Nutze dafür matplotlib (code adaptiert von https://gist.github.com/pklaus/3e16982d952969eb8a9a):

Code: Alles auswählen

import sys
from PyQt5 import QtWidgets
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

import numpy as np

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.initUI()

    def initUI(self):
        QMainWindow.__init__(self)

        self.setMinimumSize(QSize(100, 100))
        self.setWindowTitle("test")

        self.centralWidget = QWidget(self)
        self.setCentralWidget(self.centralWidget)

        self.layoutUI()

    def layoutUI(self):
        measurebtn = QPushButton('Measure', self)
        measurebtn.clicked.connect(self.showMeasurement)

    def showMeasurement(self):
        print('measure1')
        d = QDialog()
        print('measure2')
        MyDynamicPlotCanvas(self, width=8, height=4, dpi=100)
        print('measure3')
        d.setWindowModality(Qt.ApplicationModal)
        d.exec_()

class PlotCanvas(FigureCanvas):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        self.canvas = FigureCanvas(self.fig)
        self.ax1 = self.fig.add_subplot(211, facecolor=(0.94, 0.94, 0.94)) # Farbe Plotbereich
        self.fig.set_facecolor((0.94, 0.94, 0.94)) #Rahmenfarbe

        self.ax3 = self.fig.add_subplot(212, facecolor = (0.94, 0.94, 0.94))
        self.ax3.set_xticks([])
        self.ax3.set_yticks([])
        FigureCanvas.__init__(self, self.fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

class MyDynamicPlotCanvas(PlotCanvas):

    def __init__(self, *args, **kwargs):
        PlotCanvas.__init__(self, *args, **kwargs)
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.update_figure)
        self.timer.start(100)

    def update_figure(self):
        xmin = 0
        xmax = 10
        c = 100
        l = np.array([random.random() for i in range(c)])
        x = np.arange(xmin, xmax, 10/c)
        y = np.sin(x) + l/10
        y2 = y*8

        self.ax1.cla()

        self.ax1.set_title('Amplification factor ')
        self.ax1.set_xlim(-10, 10)
        self.ax1.set_ylim(-10, 10)
        self.ax1.set_xlabel('Time (s)')
        self.ax1.set_ylabel('Signal (V)')
        self.ax1.grid(color='gray', alpha=0.5, linestyle='dashed', linewidth=0.5)
        self.ax1.plot(x, y, 'y')
        self.ax1.plot(x, y2, 'b')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    w = MainWindow()
    w.show()
    app.exec_()
 
Das verlinkte Beispiel klappt, jedoch stürzt python bei mir ab, sobald ich MyDynamicPlotCanvas aufrufe. Kann den Fehler nicht finden und bin für jeden Hinweis dankbar.

Grüße
Markus
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@mushroom: Du mußt den Fehler auch nicht finden, denn der wird Dir samt Traceback ausgegeben. Es wäre gut, wenn Du den Fehler auch posten würdest, dann müßte man nicht mühsam selbst danach suchen.

Sonstiges zum Code: Keine *-Importe.
Mal rufst Du __init__ mal per super auf, mal per Klassennamen, mal einmal, mal zweimal! Dass da überhaupt was tut, ist glücklicher Zufall.
initUI und layoutUI sind zwei Methoden, die überflüssig sind, weil alles in __init__ gehört.

In `PlotCanvas` rufst Du Methoden der Elternklasse direkt auf, was nicht sein sollte. Warum gibt es ein ax1 und ein ax3 aber kein ax2? Beides sind übrigens sehr schlechte Variablennamen, weil sie nichts aussagen.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mushroom: Da wo `super()` verwendet wird, wird es übermässig kompliziert verwendet, beziehungsweise so wie man das unter Python 2 noch machen musste.

Abgesehen davon, das das Aufteilen von `MainWindow.__init__()` auf mehrere Methoden unnötig ist, macht die `layoutUI()`-Methode auch gar nicht was der Name suggeriert.

Mit `self.centralWidget` wird die Methode `centralWidget()` von `QMainWindow` verdeckt. Man braucht dieses Objekt auch gar nicht an das Objekt binden, weil man es eben über diese verdeckte Methode wieder abgragen kann.

`PlotCanvas` erbt von `FigureCanvas` hat dann aber selbst noch mal ein Attribut vom Typ `FigureCanvas`‽

Das `My` bei `MyDynamicPlotCanvas` hat keinerlei Mehrwert.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
mushroom
User
Beiträge: 58
Registriert: Sonntag 21. November 2010, 12:32

Hallo,

danke für eure Tips. Bin tatsächlich seit langem mal wieder dabei mit Python was zu machen und dann gleich objektorientiert. Die Zeit zum Einlesen/Einlernen fehlt mir leider ein wenig, da es sich um ein berufliches Projekt handelt.
Den Code habe ich ein wenig entschlackt und angepasst:

Code: Alles auswählen

import sys

from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QDialog
from PyQt5.QtCore import QSize

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

import numpy as np

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.setMinimumSize(QSize(100, 100))
        self.setWindowTitle("test")

        measurebtn = QPushButton('Measure', self)
        measurebtn.clicked.connect(self.showMeasurement)

    def showMeasurement(self):
        print('measure1')
        d = QDialog()
        print('measure2')
        DynamicPlotCanvas(self, width=8, height=4, dpi=100)
        print('measure3')
        d.setWindowModality(Qt.ApplicationModal)
        d.exec_()

class PlotCanvas(FigureCanvas):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        self.canvas = FigureCanvas(self.fig)
        self.axes = self.fig.add_subplot(211, facecolor=(0.94, 0.94, 0.94)) # Farbe Plotbereich
        self.fig.set_facecolor((0.94, 0.94, 0.94)) #Rahmenfarbe

        FigureCanvas.__init__(self, self.fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

class DynamicPlotCanvas(PlotCanvas):

    def __init__(self, *args, **kwargs):
        PlotCanvas.__init__(self, *args, **kwargs)
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.update_figure)
        self.timer.start(100)

    def update_figure(self):
        xmin = 0
        xmax = 10
        c = 100
        l = np.array([random.random() for i in range(c)])
        x = np.arange(xmin, xmax, 10/c)
        y = np.sin(x) + l/10

        self.axes.cla()
        self.axes.grid(color='gray', alpha=0.5, linestyle='dashed', linewidth=0.5)
        self.axes.plot(x, y, 'y')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    w = MainWindow()
    w.show()
    app.exec_()
 
Eine Meldung aus dem Traceback erhalte ich leider nicht, sondern Python stürzt direkt ab und ich bekomme lediglich im Editor (PyCharm) 'Process finished with exit code -1073740791 (0xC0000409)'. Deswegen hier meine Frage, wo ich am besten ansetzen kann.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Stürzt das jetzt direkt ab, oder erst nach dem Drücken des Knopfes, wie Du im ersten Beitrag hier noch behauptet hast?
Funktioniert das Programm außerhalb von PyCharm?
Funktionieren andere Qt5-Programme?

Daneben sind auch in diesem Code noch einige Programmierfehler drin, deren Meldung Du aber wohl erst bekommst, wenn Dein QT läuft.
mushroom
User
Beiträge: 58
Registriert: Sonntag 21. November 2010, 12:32

Python stürzt nach Drücken des Buttons 'Measure' ab , also nach Aufruf von '(My)DynamicPlotCanvas', so wie im ersten Beitrag geschrieben.

Andere Qt-Programme funktionieren, sind allerdings sehr simple ohne matplotlib-Integration. Das MainWindow im aktuellen Programm läuft ja auch.

Außerhalb von PyCharm erhalte ich tatsächlich einen Traceback. Hatte zunächst noch vergessen random zu importieren und ein paar Abhängigkeiten waren noch nicht korrekt eingepflegt. Löst das Problem aber nicht wirklich. Es wird zwar ein Fenster geöffnet, allerdings kein Inhalt, es werden also keine Achsen gezeichnet und auch kein Sinus geplottet. Immerhin stürzt python jetzt nicht mehr ab.

Code: Alles auswählen

import sys

from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QDialog, QSizePolicy
from PyQt5.QtCore import QSize

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

import numpy as np
import random

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.setMinimumSize(QSize(100, 100))
        self.setWindowTitle("test")

        measurebtn = QPushButton('Measure', self)
        measurebtn.clicked.connect(self.showMeasurement)

    def showMeasurement(self):
        print('measure1')
        d = QDialog()
        print('measure2')
        DynamicPlotCanvas(self, width=8, height=4, dpi=100)
        print('measure3')
        d.setWindowModality(QtCore.Qt.ApplicationModal)
        print('measure4')
        d.exec_()
        print('measure5')

class PlotCanvas(FigureCanvas):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        self.canvas = FigureCanvas(self.fig)
        self.axes = self.fig.add_subplot(211, facecolor=(0.94, 0.94, 0.94)) # Farbe Plotbereich
        self.fig.set_facecolor((0.94, 0.94, 0.94)) #Rahmenfarbe

        FigureCanvas.__init__(self, self.fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

class DynamicPlotCanvas(PlotCanvas):

    def __init__(self, *args, **kwargs):
        PlotCanvas.__init__(self, *args, **kwargs)
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.update_figure)
        self.timer.start(100)

    def update_figure(self):
        xmin = 0
        xmax = 10
        c = 100
        l = np.array([random.random() for i in range(c)])
        x = np.arange(xmin, xmax, 10/c)
        y = np.sin(x) + l/10

        self.axes.cla()
        self.axes.grid(color='gray', alpha=0.5, linestyle='dashed', linewidth=0.5)
        self.axes.plot(x, y, 'y')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    w = MainWindow()
    w.show()
    app.exec_()
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Stelle sicher, dass Du innerhalb von PyCharm auch wirklich die selben Bibliotheken benutzt wie außerhalb.
mushroom
User
Beiträge: 58
Registriert: Sonntag 21. November 2010, 12:32

Arbeite in PyCharm mit einer virtual environment. Bei Ausführung des Codes außerhalb von PyCharm habe ich mit derselben Bibliothek gearbeitet.
Antworten