BAR
BAR

Reputation: 17111

Trying to open a PyQtGraph window in PyQt5

My OptionsViz class is working on its own. However, when I throw in the asyncio stuff it doesn't show anything updating. The loop is needed for code that I have removed for brevity, so please don't throw that away.

import sys
import asyncio
from qasync import QEventLoop
from PyQt5.QtWidgets import QApplication, QMainWindow, QDockWidget

class OptionViz:
  def __init__(self, app):
    self.app = app
    self.p = pg.plot()
    self.p.setWindowTitle("pyqtgraph example: PlotSpeedTest")
    self.p.setRange(QtCore.QRectF(0, -10, 5000, 20))
    self.p.setLabel("bottom", "Index", units="B")
    self.curve = self.p.plot()

    self.data = np.random.normal(size=(50,5000))
    self.ptr = 0
    self.lastTime = time()
    self.fps = None

    timer = QtCore.QTimer()
    timer.timeout.connect(self.update)
    timer.start(0)

  def update(self):
      self.curve.setData(self.data[self.ptr%10])
      self.ptr += 1
      now = time()
      dt = now - self.lastTime
      self.lastTime = now
      if self.fps is None:
          fps = 1.0/dt
      else:
          s = np.clip(dt*3., 0, 1)
          self.fps = self.fps * (1-s) + (1.0/dt) * s
      self.p.setTitle('%0.2f fps' % fps)
      self.app.processEvents()  ## force complete redraw for every plot

async def main(app):
  # some await task here
  viz = OptionViz(app)
  # more await code here

if __name__ == '__main__':
  app = QApplication(sys.argv)
  loop = QEventLoop(app)
  asyncio.set_event_loop(loop)
  loop.create_task(main(app))
  loop.run_forever()

Upvotes: 1

Views: 797

Answers (1)

eyllanesc
eyllanesc

Reputation: 243955

Qt is not compatible with asyncio so several libraries have been implemented such as quamash, asyncqt, qasync, etc. to make it compatible. In the case of quamash and asyncqt they have a bug that does not allow to execute but the qasync library so that it has solved it(execute pip install qasync to install the package).

On the other hand, the main method is not awaitable since it does not execute a time-consuming task but it is executed instantly so I have had to restructure your project:

import sys
import asyncio
from qasync import QEventLoop
from PyQt5 import QtCore, QtWidgets
import pyqtgraph as pg
import numpy as np


class OptionViz:
    def __init__(self, app):
        self.app = app
        p = pg.plot()
        p.setWindowTitle("pyqtgraph example: PlotSpeedTest")
        p.setRange(QtCore.QRectF(0, -10, 5000, 20))
        p.setLabel("bottom", "Index", units="B")
        self.curve = p.plot()


async def main(viz):
    data = np.random.normal(size=(50, 5000))
    ptr = 0
    while True:
        viz.curve.setData(data[ptr % 10])
        await asyncio.sleep(0.1)
        ptr += 1


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)
    viz = OptionViz(app)
    loop.create_task(main(viz))
    loop.run_forever()

Again, the "main" function only creates an "OptionViz" object that does not take a long time is not awaitable so it is absurd to use async in that case. It seems that the OP does not understand the operation of asyncio.

By restructuring your code we can make the function awaitable so OptionViz must be a QObject to use the asyncSlot decorator, in addition the QTimer must be a child of the QObject so that its life cycle increases.

import sys
import asyncio
from qasync import QEventLoop, asyncSlot
from PyQt5 import QtCore, QtWidgets
import pyqtgraph as pg
import numpy as np
from time import time


class OptionViz(QtCore.QObject):
    def __init__(self, app):
        super().__init__()
        self.app = app
        self.p = pg.plot()
        self.p.setWindowTitle("pyqtgraph example: PlotSpeedTest")
        self.p.setRange(QtCore.QRectF(0, -10, 5000, 20))
        self.p.setLabel("bottom", "Index", units="B")
        self.curve = self.p.plot()

        self.data = np.random.normal(size=(50, 5000))
        self.ptr = 0
        self.lastTime = time()
        self.fps = None

        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.update)
        timer.start(0)

    @asyncSlot()
    async def update(self):
        self.curve.setData(self.data[self.ptr % 10])
        self.ptr += 1
        now = time()
        dt = now - self.lastTime
        self.lastTime = now
        if self.fps is None:
            fps = 1.0 / dt
        else:
            s = np.clip(dt * 3.0, 0, 1)
            self.fps = self.fps * (1 - s) + (1.0 / dt) * s
        self.p.setTitle("%0.2f fps" % fps)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)
    viz = OptionViz(app)
    loop.run_forever()

Upvotes: 1

Related Questions