Неправильно заполняется_бетон в matplotlib при использовании потоков - PullRequest
0 голосов
/ 15 октября 2019

Этот вопрос фактически основывается на моем вопросе, который я разместил на прошлой неделе. ( Как выполнить частичное заполнение_бетона в matplotlib, как разными цветами для разных значений )

Я пытаюсь записать количество данных в секунду на CANBus и отобразить это на графике. Я заранее извиняюсь за большое количество кода, но три метода, которые я попробовал, несколько похожи.

Это статическая ситуация, которая работает:

import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib import pyplot as plt
import sys
from collections import deque
import logging
import time

logging.basicConfig(level=logging.INFO)

buffer_size = 120
lvl = buffer_size * [100]
llvl = buffer_size * [95]
t = [t for t in range(buffer_size)]

bitthrough = deque(buffer_size*[0], buffer_size)

y = [107, 108, 105, 109, 107, 106, 107, 109, 106, 106, 94, 93, 94, 93, 93, 94, 95, 106, 108, 109, 107, 107, 106, 108, 105, 108, 107, 106, 107, 97, 93, 96, 94, 96, 95, 94, 104, 107, 106, 108, 107, 107, 106, 107, 105, 107, 108, 105, 107, 100, 93, 94, 93, 95, 104, 107, 107, 108, 108, 107, 107, 107, 107, 104, 94, 96, 95, 96, 94, 95, 94, 100, 107, 107, 105, 107, 107, 109, 107, 108, 107, 105, 108, 108, 106, 97, 94, 94, 94, 94, 95, 94, 94, 94, 96, 108, 108, 107, 106, 107, 107, 108, 107, 106, 95, 95, 95, 94, 94, 96, 105, 108, 107, 106, 106, 108, 107, 108, 106, 107]

bitthrough = y

fig, ax = plt.subplots()

ax.set_ylim(0, 120)
ax.set_xlim(0, 120)

ln, = plt.plot([], color='black')
plt.ion()
plt.show()

while True:
    plt.pause(1)

    ln.set_xdata(range(buffer_size))
    ln.set_ydata(bitthrough)

    wh_green = [a <= b for a,b in zip(bitthrough, llvl)]
    wh_orange = [a > b and a <= c for a, b, c in zip(bitthrough, llvl, lvl)]
    wh_red = [a > b for a, b, in zip(bitthrough, lvl)]

    ax.fill_between(t, 0, bitthrough, where=wh_red, color='red', interpolate=True)
    ax.fill_between(t, 0, bitthrough, where=wh_orange, color='orange', interpolate=True)
    ax.fill_between(t, 0, bitthrough, where=wh_green, color='green', interpolate=True)
    fig.canvas.draw_idle()

Это приводит кследующий график: (что хорошо для меня, сейчас) static situation graph

Но когда я хочу обновить график в реальном времени, используя потоки, вещи ломаются. Это код, который у меня есть. canbus - это модуль, который я сделал для контроля CANBus. PCAN.throughput() - функция сопрограммы asyncio, которая подсчитывает количество сообщений в секунду. После того, как второй пройден, он возвращается с количеством килобайт, полученных им в автобусе. Терминал показывает содержимое bitthrough, которое заполняется в отдельном потоке.

from canbus import PCAN
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib import pyplot as plt
import sys
import asyncio
from collections import deque
import logging
import time
import threading
from matplotlib.figure import Figure

logging.basicConfig(level=logging.INFO)

buffer_size = 120
lvl = buffer_size * [100]
llvl = buffer_size * [95]
t = [t for t in range(buffer_size)]

loop = asyncio.get_event_loop()

cn = PCAN()
loop.run_until_complete(cn.poll_ids())
loop.run_until_complete(cn.get_names())
print(cn.names)

bitthrough = deque(buffer_size*[0], buffer_size)


def get_bt(loop):
    asyncio.set_event_loop(loop)
    while True:
        task = loop.run_until_complete(cn.throughput())
        bitthrough.append(task[0] / 33. * 100.)
        print(bitthrough)


thread = threading.Thread(target=get_bt, args=(loop,))
thread.daemon = True
thread.start()

fig, ax = plt.subplots()

ax.set_ylim(0, 120)
ax.set_xlim(0, 120)

ln, = plt.plot([], color='black')
plt.ion()
plt.show()

while True:
    plt.pause(1)
    ln.set_xdata(range(buffer_size))
    ln.set_ydata(bitthrough)

    wh_green = [a <= b for a,b in zip(bitthrough, llvl)]
    wh_orange = [a > b and a <= c for a, b, c in zip(bitthrough, llvl, lvl)]
    wh_red = [a > b for a, b, in zip(bitthrough, lvl)]

    ax.fill_between(t, 0, bitthrough, where=wh_red, color='red', interpolate=True)
    ax.fill_between(t, 0, bitthrough, where=wh_orange, color='orange', interpolate=True)
    ax.fill_between(t, 0, bitthrough, where=wh_green, color='green', interpolate=True)
    fig.canvas.draw_idle()

Этот код приводит к следующему:

threading case graph

Обратите внимание, что красный цвет остается на самом высоком уровне, который был раньше.

Я также пытался реализовать его с помощью Qt backend matplotlib. Тем не менее, я только что получил пустой график, который не может быть обновлен. (Без потоков этот метод работал, но он зависал на моем компьютере, оставляя меня неспособным сделать что-то еще)

from canbus import PCAN
# import numpy as np
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
import sys
import asyncio
from collections import deque
import logging
import time
import threading
import datetime
from matplotlib.backends.qt_compat import QtCore, QtWidgets
from matplotlib.backends.backend_qt5agg import (
        FigureCanvas, NavigationToolbar2QT as NavigationToolbar)

from matplotlib.figure import Figure

logging.basicConfig(level=logging.INFO)

buffer_size = 120
lvl = buffer_size * [100]
llvl = buffer_size * [95]
t = [t for t in range(buffer_size)]

loop = asyncio.get_event_loop()

cn = PCAN()
loop.run_until_complete(cn.poll_ids())
loop.run_until_complete(cn.get_names())
print(cn.names)

bitthrough = deque(buffer_size*[0], buffer_size)


def get_bt(loop):
    asyncio.set_event_loop(loop)
    while True:
        task = loop.run_until_complete(cn.throughput())
        bitthrough.append(task[0] / 33. * 100.)
        print(bitthrough)


thread = threading.Thread(target=get_bt, args=(loop,))
thread.daemon = True
thread.start()

class ApplicationWindow(QtWidgets.QMainWindow):

    def __init__(self):

        super().__init__()

        self._main = QtWidgets.QWidget()
        self.setCentralWidget(self._main)
        layout = QtWidgets.QVBoxLayout(self._main)

        dynamic_canvas = FigureCanvas(Figure(figsize=(5, 3)))
        layout.addWidget(dynamic_canvas)

        self._dynamic_ax = dynamic_canvas.figure.subplots()

        self._update_canvas()

    def _update_canvas(self):

        # p = loop.run_until_complete(cn.throughput())[0] / 500. * 100.
        # bitthrough.append(p)

        wh_green = [a <= b for a,b in zip(bitthrough, llvl)]
        wh_orange = [a > b and a <= c for a, b, c in zip(bitthrough, llvl, lvl)]
        wh_red = [a > b for a, b, in zip(bitthrough, lvl)]

        self._dynamic_ax.clear()
        self._dynamic_ax.fill_between(t, 0, bitthrough, where=wh_red, color='red', interpolate=True)
        self._dynamic_ax.fill_between(t, 0, bitthrough, where=wh_orange,color='orange', interpolate=True)
        self._dynamic_ax.fill_between(t, 0, bitthrough, where=wh_green, color='green', interpolate=True)
        self._dynamic_ax.plot(t, bitthrough, color="black")
        self._dynamic_ax.set_ylim(0, 120)
        logging.info("redrawing graph")
        self._dynamic_ax.figure.canvas.draw()


if __name__ == "__main__":

    qapp = QtWidgets.QApplication(sys.argv)
    app = ApplicationWindow()
    app.show()
    qapp.exec_()
    while True:
        app._update_canvas()
        time.sleep(1)

, который дал мне это: qt matplotlib backend

Тоже нехорошо, и сейчас я немного растерялся.

1 Ответ

0 голосов
/ 21 октября 2019

Я обнаружил, что проблема - это мое последнее решение (с использованием Qt бэкэнда matplotlib). Я позвонил

while True:
    app._update_canvas()
    time.sleep(1)

вне приложения Qt. Таким образом, график не обновился, следовательно, пустой график. Перемещение app._update_canvas() внутрь класса и периодический вызов его с помощью QTimer правильно отображает график.

Добавлена ​​функция __init__ ApplicationWindow:

self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self._update_canvas)
self.timer.start(1000)
...