matplotlib и pyqt4: обновить график в одном потоке, нарисовать в другом - PullRequest
2 голосов
/ 03 сентября 2011

Я создаю графический интерфейс для мониторинга данных с помощью PyQt4 и matplotlib для создания графиков. Графический интерфейс отображает несколько графиков одновременно (около 6 или 7). Чтобы дать потоку GUI больше времени и немного лучшее время отклика, я только рисую canvas.draw() и все другие команды построения, которые я делаю в потоке, который обновляет данные графика. Поэтому в потоке, не связанном с графическим интерфейсом, я выполняю такие команды, как line.set_ydata, ax.set_ylim и другие, которые, возможно, потребуется обновить.

Два потока имеют доступ к объектам рисунка и холста через словарь, который передается двум потокам при инициализации. Когда поток не-GUI получает данные и обновляет график, он затем сигнализирует потоку GUI о перерисовке холста с использованием сигналов Qts (автоматическое подключение). Мой опыт работы с потоками говорит мне, что я должен использовать блокировку или убедиться, что не-GUI-поток каким-то образом заблокирован на перерисовке, но в моем порыве кодирования я никогда не вставлял его и не забывал об этом до сих пор. Ключевым моментом в этой ситуации является то, что я хочу видеть каждое обновление графика, а не перерисовывать в середине обновления другого потока или даже пропустить обновление (если это имеет смысл). В настоящее время я думаю, что мне просто повезло с выбором времени, и, кажется, все работает хорошо.

Еще одна полезная вещь - это то, что я создаю потоки, перемещая QObject в QThread с помощью moveToThread.

Мои вопросы:

  • Мне просто везет или Qt просто делает что-то волшебное?
  • Как лучше всего выполнить блокировку на холсте / фигуре matplotlib?

Вероятно, я должен отметить, что это была моя первая попытка сделать графический интерфейс более отзывчивым (перемещение команд matplotlib в поток данных), и я, возможно, перехожу к рисованному стилю анимации, который обновляет только изменяемые части графика. Но мне все еще интересно, как мне повезло.

Спасибо за любую помощь.

Обновление / Уточнение / Продолжение комментариев: Я хотел, чтобы вся система мониторов была легко изменена / обновлена ​​учеными, которые могут быть знакомы только с matlab и, возможно, matplotlib. Я не полностью против перехода на pyqwt для построения графика скорости. А что касается кадров в секунду, мне вообще не нужно много, так как данные, которые наносятся на график, поступают только через каждые 0,5 секунды номинала (0,2 секунды как минимум). Кажется, что отзывчивость GUI "съедает **", потому что там очень много графиков. Я проверил концепцию взлома моего кода с помощью matplotlib blitting, и, кажется, это очень помогает, при необходимости произойдет pyqwt. Мои предыдущие вопросы остаются в силе.

1 Ответ

1 голос
/ 29 апреля 2016

Я наткнулся на подобную проблему.У меня было много графиков для рисования (~ 250), поэтому мое окно с графическим интерфейсом в конечном итоге отображалось как зависшее в Windows.Я изменил класс рисунка для выполнения черчения в виде отдельного потока.Результат - мой графический интерфейс больше не зависает, окно печати появляется после завершения печати.Чтобы использовать его, вы создаете PlotDialog экземпляр и вызываете draw_plots метод с аргументом plot_data.plot_data - это список словарей, каждый словарь представляет подплот.Каждый субплот имеет следующие ключи (и соответствующие данные): title, xlabel, ylabel и data.Надеюсь, что это имеет смысл.

Вот мой код:

import math

from PyQt4 import QtCore, QtGui

from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar


class MyFigure(Figure, QtCore.QThread):
    def __init__(self, parent, *args, **kwargs):
        QtCore.QThread.__init__(self, parent)
        Figure.__init__(self, *args, **kwargs)

        self.plot_data = list()

    def start_plotting_thread(self, plot_data, on_finish=None):
        """ Start plotting """
        self.plot_data = plot_data

        if on_finish is not None:
            self.finished.connect(on_finish)

        self.start()

    def run(self):
        """ Run as a thread """
        # Figure out rows and columns
        total_plots = len(self.plot_data)

        columns = int(math.sqrt(total_plots))
        if columns < 1:
            columns = 1

        rows = int(total_plots / columns)
        if (total_plots % columns) > 0:
            rows += 1
        if rows < 1:
            rows = 1

        # Plot Data
        for plot_index, _plot_data in enumerate(self.plot_data):
            plot_number = plot_index + 1
            args = (rows, columns, plot_number)
            kwargs = {
                'title': _plot_data['title'],
                'xlabel': _plot_data['xlabel'],
                'ylabel': _plot_data['ylabel']
            }

            figure = self.add_subplot(*args, **kwargs)

            figure.plot(_plot_data['data'])


class PlotDialog(QtGui.QDialog):
    def __init__(self, parent):
        super(PlotDialog, self).__init__(parent, QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint)

        self.figure = MyFigure(self)
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self)

        self.layout = QtGui.QGridLayout()
        self.setLayout(self.layout)

        layout = [
            [self.canvas],
            [self.toolbar],
        ]

        for row_index, columns in enumerate(layout):
            if type(columns) is list:
                for column_index, widget in enumerate(columns):
                    if widget is not None:
                        self.layout.addWidget(widget, row_index, column_index)

    def draw_plots(self, plot_data):
        """ Plot Plots """
        self.figure.start_plotting_thread(plot_data, on_finish=self.finish_drawing_plots)

    def finish_drawing_plots(self):
        """ Finish drawing plots """
        self.canvas.draw()
        self.show()
...