Schwierigkeiten mit dem Plotten

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
E-Ray96
User
Beiträge: 1
Registriert: Donnerstag 3. August 2023, 17:12

Hallo Leute,
ich bin neu in diesem Forum und hoffe, dass ich hier richtig bin :)

Folgendes Anliegen:
Ich habe mittels Python und QtCreator eine eine GUI entwickelt, die Echtzeit-Daten von Thermoelementen und Druckmessonden aufnimmt und die aktuellsten Messungen kontinuierlich in der GUI darstellt. Diese Messergebnisse werden zudem dazu genutzt um im Hintergrund den Volumenstrom auszurechnen. Mein Ziel ist es, dass am Ende zwei Outputs generiert werden. Zum einen soll eine csv.-Datei erstellt werden, die besagte Echtzeitmessungen beinhaltet, wenn ich den gesamten Messvorgang (mit Messungen beenden button) terminiere --> Das funktioniert auch super.

Der zweite Output soll ein Plot sein, der auf der x-Achse die Runtime in Sekunden zeigt, auf der primären Y-Achse die Temperaturen (der Parameter heißt t_input in Zeile 70) und auf der sekundären Y-Achse den Volumenstrom (der Parameter heißt qv in Zeile 132).
Eigentlich ist das ein sehr einfaches Problem in meinen Augen. Denn ich weiß wie man plots erstellt, aber hier will es einfach nicht funktionieren. Meine bisherigen Versuche hatten immer zur Folge, das mit jeder Iteration quasi eine neue Figure erstellt wird.
Ich werde euch auch den Code geben und hoffe, dass ihr den versteht.


Falls ihr irgendwelche Fragen etc. habt, lasst es mich bitte wissen.

Ich hoffe Ihr könnt mir helfen und bin jetzt schon für jede Hilfe dankbar.

Beste Grüße

Code: Alles auswählen


#############################################    Imports für QtPy    ##################################################
from qtpy import QtWidgets
from frm_ProjectC3.mainwindow import Ui_MainWindow
from PyQt5.QtCore import QTimer
#############################################    Import für Messelemente (ADAM), Plotting und CSV-Erstellung   ##################################################
import serial, math, time, sys, csv
from datetime import datetime
import matplotlib.pyplot as plt
import numpy as np
#############################################    Globale Parameter   ##################################################
pi = math.pi
date = datetime.now()                                       # Getting the current date and time - Soll dazu genutzt werden, um der txt-Datei automatisert einen Namen geben zu können.
datetime_for_csv = date.strftime("%m%d%Y_%H_%M_%S")         # Soll genutzt werden um in die CSV Datei das richtige Datum zu printen

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.setWindowTitle("Projekt: C3")
        self.ui = Ui_MainWindow()

        self.ui.setupUi(self)
        self.timer = QTimer()
        self.ser = serial.Serial()
        self.ser.baudrate = 9600
        self.ser.port = self.ui.cbBox_serialPort.currentText()

        #Es benötigt die changed Funktion "currentTextChanged" um den Wert des Com-Ports neu zu setzen.
        self.ui.cbBox_serialPort.currentTextChanged.connect(self.on_combobox_changed)
        self.ser.stopbits = 1
        self.ser.bytesize = 8
        self.ser.parity = 'N'


        self.ui.btn_MessungStarten.clicked.connect(self.btn_messungStarten_click) #Führt den Button "Messung Starten" aus.
        #self.ui.btn_MessungStarten.clicked.connect(self.update_plot)
        self.ui.btn_MessungBeenden.clicked.connect(self.btn_messungBeenden_click) #Führt den Button "Messung Beenden" aus.

    # currentTextChanged Abfrage des aktuellen Wertes
    def on_combobox_changed(self):
        self.ser.port = self.ui.cbBox_serialPort.currentText()

    def showTime(self):
        #Hier werden die Temperaturen durch die entsprechenden ADAM-Controller ausgelesen.
        self.ser.write('#00\r'.encode('utf-8'))
        s1 = self.ser.read_until('\r'.encode('utf-8'), size=None)  # all channels
        t = []
        for i in range(1, len(s1) - 2, 7):
            t.append(float(s1[i:i + 7]))


        #Hier werden die Drücke durch die entsprechenden ADAM-Controller ausgelesen (ABER noch in VOLT).
        self.ser.write('#02\r'.encode('utf-8'))
        s2 = self.ser.read_until('\r'.encode('utf-8'), size=None)  # specific channel
        p = []
        for i in range(1, len(s2) - 2, 7):
            p.append(float(s2[i:i + 7]))

        ############################################################  Hier werden die Temp_Labels mit dem aktuellsten Wert beschrieben.   ###########################################################
        self.ui.temp_0_output.setText(str(round(t[0],2)))
        self.ui.ambientTemp_lbl.setText(str(round(t[0],2)))
        self.ui.temp_1_output.setText(str(round(t[1],2)))
        self.ui.temp_2_output.setText(str(round(t[2],2)))
        self.ui.temp_3_output.setText(str(round(t[3],2)))
        self.ui.temp_4_output.setText(str(round(t[4],2)))
        self.ui.temp_5_output.setText(str(round(t[5],2)))
        self.ui.temp_6_output.setText(str(round(t[6],2)))
        self.ui.temp_7_output.setText(str(round(t[7],2)))

        ############################################################  Arithmetisches Mittel der Temp_1-3 ergeben T_input (Temperatur über Schüttgut)  ###########################################################
        t_input = round(((t[1] + t[2] + t[3]) / 3), 2)
        self.ui.temp_aboveBulk_lbl.setText(str(t_input))

        ########################################################### Hier werden die Press_Labels mit dem aktuellsten Wert beschrieben --> Werden aufgrund der Linearität von Volt in [Pa] umgerechnet werden.   ###########################################################
        press_1 = round(1.013 * 10 ** 5 + (p[0] * 20 * 100), 2)
        self.ui.press_1_output.setText(str(press_1))
        d_press_1 = round(1.013 * 10 ** 5 + (p[1] * 5 * 100), 2)
        self.ui.press_dp1_output.setText(str(d_press_1))
        d_press_2 = round(1.013 * 10 ** 5 + (p[2] * 5 * 100), 2)
        self.ui.press_dp2_output.setText(str(d_press_2))
        d_press_3 = round(1.013 * 10 ** 5 + (p[3] * 5 * 100), 2)
        self.ui.press_dp3_output.setText(str(d_press_3))

        ########################################################### Hier beginnt die Beschriftung der Tabelle ###########################################################
        # Anzahl der Tabellen-Zeilen:
        position = self.ui.dataOutput_tbl.rowCount()
        self.ui.dataOutput_tbl.insertRow(position)
        # self.ui.dataOutput_tbl.setItem(row, column,item) --> row = position, column = 0 - 13 [0 = Datum; 1-8 = Temps.; 9-13 = Drücke], und item sind die für die jeweilige Spalte zu übergebenden Daten
        self.ui.dataOutput_tbl.setItem(position, 0, QtWidgets.QTableWidgetItem(datetime_for_csv))
        self.ui.dataOutput_tbl.setItem(position, 1, QtWidgets.QTableWidgetItem(str(round(t[0],2))))
        self.ui.dataOutput_tbl.setItem(position, 2, QtWidgets.QTableWidgetItem(str(round(t[1],2))))
        self.ui.dataOutput_tbl.setItem(position, 3, QtWidgets.QTableWidgetItem(str(round(t[2],2))))
        self.ui.dataOutput_tbl.setItem(position, 4, QtWidgets.QTableWidgetItem(str(round(t[3],2))))
        self.ui.dataOutput_tbl.setItem(position, 5, QtWidgets.QTableWidgetItem(str(round(t[4],2))))
        self.ui.dataOutput_tbl.setItem(position, 6, QtWidgets.QTableWidgetItem(str(round(t[5],2))))
        self.ui.dataOutput_tbl.setItem(position, 7, QtWidgets.QTableWidgetItem(str(round(t[6],2))))
        self.ui.dataOutput_tbl.setItem(position, 8, QtWidgets.QTableWidgetItem(str(round(t[7],2))))
        self.ui.dataOutput_tbl.setItem(position, 9, QtWidgets.QTableWidgetItem(str(press_1)))
        self.ui.dataOutput_tbl.setItem(position, 10, QtWidgets.QTableWidgetItem(str(d_press_1)))
        self.ui.dataOutput_tbl.setItem(position, 11, QtWidgets.QTableWidgetItem(str(d_press_2)))
        self.ui.dataOutput_tbl.setItem(position, 12, QtWidgets.QTableWidgetItem(str(d_press_3)))


        ###########################################################   Hier kommt die Implementierung der Volumenstromberechnung.  ###########################################################
        if self.ui.feuchtigkeit_umg_txtfield.text() == "":
            phi_umg = 60
        else:
            phi_umg = float(self.ui.feuchtigkeit_umg_txtfield.text())

        p_umg = 1.013 * 10 ** 5                    # Umgebungsdruck [Pa]
        dBlende = 0.040                            # Blendendurchmesser [m]
        dRohr = 0.054                              # Rohrdurchmesser [m]
        Kappa = 1.4                                # Isentropenkoeffizient
        beta = dBlende / dRohr                     # Durchmesserverhältnis
        p2 = press_1 - d_press_3  # Druck nach der Blende (Achtung Wirkdruck) TODO: Welche Drücke sind hier genau gemeint - press_1 und press_3 richtig????
        dynVis = (0.0000000119249 * t[0] ** 3 - 0.0000263646 * t[0] ** 2 + 0.0487178 * t[0] + 17.2638) * 1e-6  # dyn. Viskosität am Blendeneintritt (in diesem Druckbereich im wesentlichen nur Temperaturabhängig)
        self.ui.dynVis_output.setText(str(dynVis))
        psu = 6.11213 ** (17.5043 * t[0] / (241.2 + t[0]))  # Sättigungsdampfdruck bei Umgebungstemperatur[°C]
        self.ui.saettdampfdruck_umgebtemp_output.setText(str(round(psu,4)))
        xu = 0.622 * psu / ((p_umg / (phi_umg/100)) - psu)  # Wassergehalt
        self.ui.wassergehalt_output.setText(str(round(xu,8)))
        pse = 6.11213 ** (17.5043 * t[0] / (241.2 + t[0]))  # Sättigungsdampfdruck bei Eintrittstemperatur [°C] #TODO: hier muss glaube eine andere Temp. anstelle von t[0] hin!!!
        self.ui.saettdampfdruck_eintrittstemp_output.setText(str(round(pse,4)))
        phie = (p_umg + press_1) * xu / (pse * (0.622 + xu))  # Luftfeuchtigkeit Blendeneintritt
        self.ui.luftfeuchtigkeit_blendeintritt_output.setText(str(round(phie,5)))
        R = 287.058 / (1.0 - phie * pse * 0.377 / press_1)  # spez. Gaskonstante
        rho = press_1 / ((t[0] + 273.15) * R)  # Dichte der Luft
        epsilon = 1.0 - (0.351 + 0.256 * pow(beta, 4.0) + 0.93 * pow(beta, 8.0)) * (1.0 - pow((p2 / press_1), (1.0 / Kappa)))  # Expansionszahl (Korrekturfaktor)
        c = 0.6
        A1 = epsilon * pow(dBlende, 2.0) * pow((2.0 * d_press_3 * rho), 0.5) / (dynVis * dRohr * pow((1.0 - pow(beta, 4.0)), 0.5)) #TODO: press_3 hier richtig???
        x1 = c * A1
        qm = pi / 4.0 * dynVis * dRohr * x1  # Massenstrom [kg/s]
        qv = qm / rho  # Volumenstrom [m³/s]
        self.ui.volumenstrom_output.setText(str(qv))




        ###########################################################   Implementation of PLOT  ###########################################################



































    def btn_messungStarten_click(self):
        try:
            self.ser.open()
            self.timer.start(1000)
            self.timer.timeout.connect(self.showTime)
            self.ui.com_errormsg_lbl.setText("Sie nutzen " + self.ser.port)
            self.ui.com_errormsg_lbl.setStyleSheet("color: green")
            self.ui.com_finally_lbl.setText("Die Messung wird gestartet!")
            self.ui.com_finally_lbl.setStyleSheet("color: green")
            self.ui.btn_MessungStarten.setDisabled(True)

        except: 
            if self.ser.port == "--Auswählen--":
                self.ui.com_errormsg_lbl.setText("Bitte wählen Sie einen geeigneten COM-Port.")
                self.ui.com_errormsg_lbl.setStyleSheet("color: red")
                self.ui.com_finally_lbl.setText("")
            else:
                self.ui.com_errormsg_lbl.setText("Sie adressieren einen unbelegten COM-Port: " + self.ser.port)
                self.ui.com_errormsg_lbl.setStyleSheet("color: red")
                self.ui.com_finally_lbl.setText("")

    def btn_messungBeenden_click(self):
        if self.ser.port == "--Auswählen--":
            self.ui.com_errormsg_lbl.setText("Sie haben auf 'Messung stoppen' gedrückt")
            self.ui.com_errormsg_lbl.setStyleSheet("color: blue")
            self.ui.com_finally_lbl.setText("Das Programm wurde erfolgreich beendet!")
            self.ui.com_finally_lbl.setStyleSheet("color: green")
            self.timer.stop()
            self.ser.close()
        else:
            self.ui.com_errormsg_lbl.setText("Beendet: " + self.ser.port)
            self.ui.com_errormsg_lbl.setStyleSheet("color: blue")
            self.ui.com_finally_lbl.setText("Das Programm wurde erfolgreich beendet!")
            self.ui.com_finally_lbl.setStyleSheet("color: green")
            self.timer.stop()
            self.ser.close()

        path, ok = QtWidgets.QFileDialog.getSaveFileName(self, "'Data_Output' speichern",datetime_for_csv, '(*.txt)')
        if ok:
            columns = range(self.ui.dataOutput_tbl.columnCount())
            header = [self.ui.dataOutput_tbl.horizontalHeaderItem(column).text() for column in columns]
            with open(path, 'w', newline="") as csvfile:
                writer = csv.writer(csvfile, delimiter=';', escapechar=' ', quoting=csv.QUOTE_NONE)
                writer.writerow(header)
                for row in range(self.ui.dataOutput_tbl.rowCount()):
                    writer.writerow(self.ui.dataOutput_tbl.item(row, column).text() for column in columns)

        self.ui.btn_MessungStarten.setDisabled(False)




#Erstellt die GUI bzw. lädt die generierten Elemente aus der mainwindow.py
if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())



Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Das erste, was Dir auffallen sollte, ist, dass Du erklären mußt, was Deine kryptischen Variablennamen bedeuten, warum ›t_input‹ statt ›temperaturen‹, oder ›qv‹ statt ›volumenstrom‹.
Kommentare werden mit einem # gekennzeichnet, nicht mit 46. Kommentare sollen dem Leser einen Mehrwert geben, indem geschrieben wird warum etwas gemacht wird, denn was da gemacht wird steht ja schon im Code.
Wenn Du pi importieren willst, dann schreib einfach ›from math import pi‹, oder besser, benutze einfach ›np.pi‹.
Konstanten werden GROSS geschrieben, Datum in Dateinamen sollte %Y%m%d sortiert sein, nicht %m%d%Y, weil das die Sortierung kaputt macht:
Das Fensterlayout wird direkt aus ui-Dateien geladen, und nicht erst nach Python konvertiert.
Eine serielle Verbindung heißt ›serial_connection‹, nicht einfach nur ‹ser›, die Parameter übergibt man gleich beim Erzeugen und setzt sie nicht nachträglich.
"--Auswählen--" ist kein sinnvoller Port, es macht also keinen Sinn, ein Serial-Objekt mit kaputten Parametern zu erzeugen. Da das ja eh erst bei der Messung gebraucht wird, sollte es auch erst dann erzeugt werden.
Nackte excepts benutzt man nicht, weil das alles abfängt, auch viele Programmierfehler, Exceptions immer so konkret angeben, wie möglich und in einem so kleinen Bereich, wie nötig.

Und jetzt zum kritischsten Teil, der Funktion showTime:
showTime ist ein schlechter Name für eine Funktion, die Daten von der seriellen Schnittstelle liest.
Benutze keine einbuchstabigen Variablenname, s1 ist eigentlich eine gelesene Zeile ›line‹.
›t‹ sind wahrscheinliche ›temperatures‹ sein, ›p‹ ›powers‹.
Gerundet wird bei der Ausgabe, nicht per round.
Man benutzt Schleifen, statt Code vielfach zu kopieren.
Zahlenwerte stehen nicht einfach mittem im Code, sondern werden als Konstanten definiert und am besten ganz am Anfang der Datei gesammelt.
Bei vielen Zahlen konnte ich ja erraten, was sie bedeuten, bei ein paar aber auch nicht.

Code: Alles auswählen

TEMPERATURE_KELVIN = 273.15
NORMAL_PRESS = 1.013e5 # Umgebungsdruck [Pa]
VOLT_TO_PASCAL = 2000
DIAMETER_PIPE = 0.054 # Rohrdurchmesser [m]
DIAMETER_BLENDE = 0.040 # Blendendurchmesser [m]
KAPPA = 1.4 # Isentropenkoeffizient
VISCOSITY_COEFFS = [0.0000000119249, -0.0000263646, 0.0487178, 17.2638]
EXPANSION_COEFFS = [0.93, 0.256, 0.351]
GAS_CONSTANT_AIR = 287.052874

SOME_NUMBER = 6.11213
SOME_FACTOR = 17.5043
SOME_TEMPERATURE = 241.2
ANOTHER_NUMBER = 0.622
ANOTHER_FACTOR = 0.377

def calculate(temperatures, pressures, phi_umg):
    # Durchmesserverhältnis
    beta = DIAMETER_BLENDE / DIAMETER_PIPE
    # Druck nach der Blende (Achtung Wirkdruck) TODO: Welche Drücke sind hier genau gemeint - press_1 und press_3 richtig????
    p2 = pressures[0] - pressures[3]
    # dyn. Viskosität am Blendeneintritt (in diesem Druckbereich im wesentlichen nur Temperaturabhängig)
    dynamic_viscosity = numpy.polyval(VISCOSITY_COEFFS, temperatures[0]) * 1e-6
    # Sättigungsdampfdruck bei Umgebungstemperatur[°C]
    psu = SOME_NUMBER ** (SOME_FACTOR * temperatures[0] / (SOME_TEMPERATURE + temperatures[0]))
    # Sättigungsdampfdruck bei Eintrittstemperatur [°C]
    # TODO: hier muss glaube eine andere Temp. anstelle von t[0] hin!!!
    pse = SOME_NUMBER ** (SOME_FACTOR * temperatures[0] / (SOME_TEMPERATURE + temperatures[0]))

    # Wassergehalt
    xu = ANOTHER_NUMBER * psu / ((NORMAL_PRESS / (phi_umg / 100)) - psu)
    # Luftfeuchtigkeit Blendeneintritt
    phie = (NORMAL_PRESS + pressures[0]) * xu / (pse * (ANOTHER_NUMBER + xu))
    # spez. Gaskonstante
    R = GAS_CONSTANT_AIR / (1 - phie * pse * ANOTHER_FACTOR / pressures[0])
    # Dichte der Luft
    rho = pressures[0] / ((temperatures[0] + TEMPERATURE_KELVIN) * R)
    # Expansionszahl (Korrekturfaktor)
    epsilon = 1 - numpy.polyval(EXPANSION_COEFFS, beta ** 4) * (1 - (p2 / pressures[0]) ** (1 / KAPPA))
    # TODO: press_3 hier richtig???
    a = epsilon * DIAMETER_BLENDE ** 2 * (2 * pressures[3] * rho) ** 0.5 / (dynamic_viscosity * DIAMETER_PIPE * (1 - beta ** 4) ** 0.5)
    mass_flow = np.pi * DIAMETER_PIPE / 4 * dynamic_viscosity * 0.6 * a # Massenstrom [kg/s]
    volume_flow = mass_flow / rho  # Volumenstrom [m³/s]
    return dynamic_viscosity, psu, xu, pse, phie, volume_flow


def read_numbers(connection, number):
    connection.write(b'#%02d\r' % number)
    line = connection.read_until(b'\r')
    return [
        float(line[i:i+7])
        for i in range(i, len(line) - 2, 7)
    ]

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent = None):
        ...
        self.temperature_outputs = [
            self.ui.temp_0_output,
            self.ui.temp_1_output,
            self.ui.temp_2_output,
            self.ui.temp_3_output,
            self.ui.temp_4_output,
            self.ui.temp_5_output,
            self.ui.temp_6_output,
            self.ui.temp_7_output,
        ]

        self.press_outputs = [
            self.press_1_output,
            self.press_dp1_output,
            self.press_dp2_output,
            self.press_dp3_output,
        ]

    def read_measurement(self):
        temperatures = read_numbers(self.ser, 0)
        powers = read_numbers(self.ser, 0)

        self.ui.ambientTemp_lbl.setText(f"{temperatures[0]:.2f}")
        for output, temperature in zip(self.temperature_outputs, temperatures):
            output.setText(f"{temperature:.2f}")

        # Arithmetisches Mittel der Temp_1-3 ergeben T_input (Temperatur über Schüttgut)
        temperature_above_bulk = np.mean(temperatures[1:4])
        self.ui.temp_aboveBulk_lbl.setText(f"{temperature_above_bulk:.2f}")

        # Drücke werden aufgrund der Linearität von Volt in [Pa] umgerechnet werden.
        pressures = [
            NORMAL_PRESS + power * VOLT_TO_PASCAL
            for power in powers
        ]
        for output, press in zip(self.press_outputs, pressures):
            output.setText(f"{press}.2f")

        position = self.ui.dataOutput_tbl.rowCount()
        self.ui.dataOutput_tbl.insertRow(position)
        values = [self.date] + [f"{t:.2f}" for t in temperatures] + [f"{p:.2f}" for p in pressures]
        for index, value in enumerate(values):
            self.ui.dataOutput_tbl.setItem(position, index, QtWidgets.QTableWidgetItem(value))

        umg = self.ui.feuchtigkeit_umg_txtfield.text()
        phi_umg = float(umg or 60)

        dynamic_viscosity, psu, xu, pse, phie, volume_flow = calculate(temperatures, pressures, phi_umg)

        self.ui.dynVis_output.setText(f"{dynamic_viscosity}")
        self.ui.saettdampfdruck_umgebtemp_output.setText(f"{psu:.4f}")
        self.ui.wassergehalt_output.setText(f"{xu:.8f}")
        self.ui.saettdampfdruck_eintrittstemp_output.setText(f"{pse:.4f}")
        self.ui.luftfeuchtigkeit_blendeintritt_output.setText(f"{phie:.5f}")
        self.ui.volumenstrom_output.setText(f"{volume_flow}")
Den Code zum Plotten habe ich bei Dir gar nicht gefunden, da sind nur Leerzeilen??
Was hast Du denn versucht?
Antworten