I am TRYING to make an app where I want to have several PlotWidgets that plot the signal from up to 5 sensors I've got in my Arduino. As soon as I have two updating plots, the GUI does not respond, and I need to pause/restart the plotting, and popping up alerts for some values. To solve that, I have started researching in order to use QThread, but that might be impossible with PyQtGraph, since we cannot have plotting done in multiple threads? My code for two PlotWidgets looks like:
from PyQt4 import QtCore, QtGui
import pyqtgraph as pg
import random
import sys
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
layout = QtGui.QHBoxLayout()
self.button = QtGui.QPushButton('Start Plotting Left')
layout.addWidget(self.button)
self.button.clicked.connect(self.plotter)
self.button2 = QtGui.QPushButton('Start Plotting Right')
layout.addWidget(self.button2)
self.button2.clicked.connect(self.plotter2)
self.plot = pg.PlotWidget()
layout.addWidget(self.plot)
self.plot2 = pg.PlotWidget()
layout.addWidget(self.plot2)
self.setLayout(layout)
def plotter(self):
self.data =[0]
self.curve = self.plot.getPlotItem().plot()
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updater)
self.timer.start(0)
def updater(self):
self.data.append(self.data[-1]+0.2*(0.5-random.random()) )
self.curve.setData(self.data)#Downsampling does not help
def plotter2(self):
self.data2 =[0]
self.curve2 = self.plot2.getPlotItem().plot()
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updater2)
self.timer.start(0)
def updater2(self):
self.data2.append(self.data[-1]+0.2*(0.5-random.random()) )
self.curve2.setData(self.data) #Downsampling does not help
if __name__ == '__main__':
app = QtGui.QApplication([])
window = MainWindow()
window.show()
app.exec_()
I am ready to read and try a lot from QThread, but first I need to know if it's possible, or if I am wasting my days and sleep. Does anybody have a hint of how could I get it to work?
Your code has a couple of typographical errors that stops it from working
Inside updater2 you are using self.data instead of self.data2. The code should be:
def updater2(self):
self.data2.append(self.data2[-1]+0.2*(0.5-random.random()) )
self.curve2.setData(self.data2) #Downsampling does not help
Also, when creating your second timer, you store it in the same variable as the first timer, which causes it to stop. The corrected code should read:
def plotter2(self):
self.data2 =[0]
self.curve2 = self.plot2.getPlotItem().plot()
self.timer2 = QtCore.QTimer()
self.timer2.timeout.connect(self.updater2)
self.timer2.start(0)
Note that "starting" a timer after it is already started (aka clicking the same button twice) causes the program to crash for me. You should probably disable the buttons, or have a second click stop the timer or something. It is up to you.
In regards to threading, you may see some performance gain from threading by reading the data in from the arduino over serial in another thread (the GUI won't lock up), but you will need to send the data via a PyQt signal to the main thread and run the plotting command there. There are many examples on StackOverflow on how to thread properly with PyQt (for instance here)
Related
I'm trying to write a PyQt application for some image processing algorithms that I have written. The problem is, these take some time and make the app freezes (until it finishes but the user might be compelled to close the app in the mean time).
I'm trying to understand how to implement multi-threading but I just can't seem to make it work. This example just loads a large image on a different thread, but it still makes the whole app freeze. I'm sure I'm making a mistake somewhere.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class App(QWidget):
def __init__(self):
super().__init__()
self._unit_ui()
def _unit_ui(self):
label = QLabel(self)
label.resize(700, 700)
button = QPushButton(self)
self.th = Thread(self)
self.th.change_pixmap.connect(label.setPixmap)
button.pressed.connect(self.th.start)
self.show()
class Thread(QThread):
change_pixmap = pyqtSignal(QPixmap)
def __init__(self, parent=None):
QThread.__init__(self, parent=parent)
self.isRunning = True
def run(self):
pixmap = QPixmap('gridscan.jpg')
self.change_pixmap.emit(pixmap)
app = QApplication(sys.argv)
m = App()
sys.exit(app.exec_())
With the help of eyllanesc I managed to solve it.
I just stopped using QPixmap in my second thread and imported the image with another function, passed it (changing the pyqtSignal(QPixmap) to pyqtSignal(np.ndarray)) and now it works flawlessly!
Thanks!
You can try looking into this answer:
https://stackoverflow.com/a/38003561/9066493
Basically, self.th needs to be moved to a different thread like:
# Setup the worker object and the worker_thread.
self.worker = WorkerObject()
self.worker_thread = QtCore.QThread()
self.worker.moveToThread(self.worker_thread)
self.worker_thread.start()
And then transfer data between the main thread and the worker thread using signal and slots, which you need to connect to a specific method slot
Hope the example helps.
I am creating an alarm clock in python using PyQt4 and in that I am using LCD display widget, which display current updating time. For that I am using threading. But I am new to it so the problem is I have no clue how to debug that thing.
This is my code
import sys
from PyQt4 import QtGui, uic
import time
import os
from threading import Thread
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
uic.loadUi('AlarmClock_UI.ui', self)
self.show()
self.comboBox.setCurrentIndex(0)
self.comboBox.currentIndexChanged.connect(self.getSelection)
self.lineEdit.setText('Please select the reminder type')
timeThread = Thread(target = self.showTime())
timeThread.start()
def getSelection(self):
if self.comboBox.currentIndex() == 1:
self.lineEdit.setText('Select the alarm time of your choice')
elif self.comboBox.currentIndex() == 2:
self.lineEdit.setText('Use those dials to adjust hour and minutes')
else:
self.lineEdit.setText('Please select the reminder type')
def showTime(self):
showTime = time.strftime('%H:%M:%S')
self.lcdNumber.display(showTime)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MyWindow()
sys.exit(app.exec_())
I tried while loop in showTime() function then it was not even loading GUI just running in the background.
Thanks :)
As has been said elsewhere, you do not need to use threading for this, as a simple timer will do. Here is a basic demo script:
import sys
from PyQt4 import QtCore, QtGui
class Clock(QtGui.QLCDNumber):
def __init__(self):
super(Clock, self).__init__(8)
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.showTime)
self.timer.start(1000)
self.showTime()
def showTime(self):
time = QtCore.QTime.currentTime()
self.display(time.toString('hh:mm:ss'))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Clock()
window.setWindowTitle('Clock')
window.setGeometry(500, 100, 400, 100)
window.show()
sys.exit(app.exec_())
Qt does not support doing GUI operations in threads other than the main thread. So when you call self.lcddisplay.display(showTime) from within the context of your spawned thread, that is an error and Qt will not work correctly.
As tdelaney suggested in his comment, the best way to handle this sort of thing is to use a QTimer to emit a signal at the appropriate intervals, and update your lcddisplay in the slot that signal is connected to.
(if you insist on using threads, however, e.g. as a learning exercise, then your spawned thread would need to send a message to the main thread to tell the main thread to do the display update, rather than trying to do the update itself)
I'm very new to Python and I have made a very simple countdown timer. The GUI was created in Qt Designer. There is a spin box for input of seconds, a start button and an LCD number counter. The counter counts down fine using the code below:
def start_btn_clicked(self):
x = self.Minute_spinBox.value()
for i in xrange(x,0,-1):
time.sleep(1)
print (i)
So that I could see what was happening as I played around with it, I added the print instruction so that it shows the countdown in the Python console as it runs. I then thought I could maybe quite easily have the LCD number display the countdown with something like:
self.lcdNumber.display(i)("%SS")
But no matter what I try, I cant get it to show. With the line above, I get the first number displayed, but then I get an error saying:
self.lcdNumber.display(i)("%SS")
TypeError: 'NoneType' object is not callable
I have tried so many variations that I no longer know where I started and here was me thinking it would be simple. I'd love to know why I cant get it to display the countdown.
Just adding one line of code to my original code will allow the lcdNumber to display the count...
def start_btn_clicked(self):
x = self.Minute_spinBox.value()
for i in xrange(x,0,-1):
time.sleep(1)
app.processEvents() # just this one line allows display of 'i'
self.lcdNumber.display(i)`
And works perfectly
The display function returns None, so doing None("%SS") obviously isn't allowed.
self.lcdNumber.display(i) is enough to show the countdown!
To let Qt paint the widgets while looping run the countdown from another thread. See an example.
import time
from threading import Thread
from PyQt4.QtGui import QApplication, QMainWindow, QLCDNumber
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.lcdnumber = QLCDNumber(self)
self.resize(400, 400)
t = Thread(target=self._countdown)
t.start()
def _countdown(self):
x = 10
for i in xrange(x,0,-1):
time.sleep(1)
self.lcdnumber.display(i)
if __name__ == "__main__":
app = QApplication([])
window = Window()
window.show()
app.exec_()
The for loop is blocking the GUI.
The slot connected to the button's clicked signal is processed synchronously. This means the event-loop must wait for the slot to return before it can process any more events (including the paint events needed for updating the GUI).
So you need to find a way to process these events whilst the for loop is running. There are various ways of doing this, such as using a QTimer or a QThread. But the simplest way of fixing your particular example would be to use QCoreApplication.processEvents.
Here's an example that shows how to do that:
import sys, time
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.spinbox = QtGui.QSpinBox(self)
self.spinbox.setValue(5)
self.lcdnumber = QtGui.QLCDNumber(self)
self.button = QtGui.QPushButton('Start', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.spinbox)
layout.addWidget(self.lcdnumber)
layout.addWidget(self.button)
def handleButton(self):
for tick in range(self.spinbox.value(), -1, -1):
self.lcdnumber.display(tick)
self.button.setEnabled(not tick)
# continually process events for one second
start = time.time()
while time.time() - start < 1:
QtGui.qApp.processEvents()
time.sleep(0.02)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 300, 200)
window.show()
sys.exit(app.exec_())
Let's say I have a PyQt program that goes through a given directory, looks for *JPEG images, and does some processing every time it finds one. Depending on the size of the selected directory, this may take from some seconds to minutes.
I would like to keep my user updated with the status - preferably with something like "x files processed out of y files" . If not, a simple running pulse progress bar by setting progressbar.setRange(0,0) works too.
From my understanding, in order to prevent my GUI from freezing, I will need a seperate thread that process the images, and the original thread that updates the GUI every interval.
But I am wondering if there is any possible way for me to do both in the same thread?
Yes, you can easily do this using processEvents, which is provided for this exact purpose.
I have used this technique for implementing a simple find-in-files dialog box. All you need to do is launch the function that processes the files with a single-shot timer, and then periodically call processEvents in the loop. This is should be good enough to update a counter with the number of files processed, and also allow the user to cancel the process, if necessary.
The only real issue is deciding on how frequently to call processEvents. The more often you call it, the more responsive the GUI will be - but this comes at the cost of considerably slowing the processing of the files. So you may have to experiment a little bit in order to find an acceptable compromise.
UPDATE:
Here's a simple demo that shows how the code could be structured:
import sys, time
from PyQt5 import QtWidgets, QtCore
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton('Start')
self.progress = QtWidgets.QLabel('0')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.progress)
self.button.clicked.connect(self.test)
self._stop = False
self._stopped = True
def test(self):
if self._stopped:
self._stop = False
self.progress.setText('0')
self.button.setText('Stop')
QtCore.QTimer.singleShot(1, self.process)
else:
self._stop = True
def process(self):
self._stopped = False
for index in range(1, 1000):
time.sleep(0.01)
self.progress.setText(str(index))
if not index % 20:
QtWidgets.qApp.processEvents(
QtCore.QEventLoop.AllEvents, 50)
if self._stop:
break
self._stopped = True
self.button.setText('Start')
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I could not achieve the thing you need without multi threading and this is not possible because gui can be only updated in main thread. Below is an algorithm how I did this with multithreading.
Let's say you have your application processing images. Then there are the following threads:
Main thread (that blocks by GUI/QApplication-derived classes.exec())
Timer with, for example, 1 second interval which updates a variable and calls a slot in GUI thread which updates a variable in user interface.
A thread which is processing images on your pc.
def process(self):
self._status = "processing image 1"
....
def _update(self):
self.status_label.setText(self._status)
def start_processing(self, image_path):
# create thread for process and run it
# create thread for updating by using QtCore.QTimer()
# connect qtimer triggered signal to and `self._update()` slot
# connect image processing thread (use connect signal to any slot, in this example I'll stop timer after processing thread finishes)
#pyqtSlot()
def _stop_timer():
self._qtimer.stop()
self._qtimer = None
_update_thread.finished.connect(_stop_timer)
In pyqt5 it is possible to assign a pyqtvariable from a one nested thread(first level). So you can make your variable a pyqtvariable with setter and getter and update gui in a setter or think how you can do this by yourself.
You could just use the python threading module and emit a signal in your threaded routine.
Here's a working example
from PyQt4 import QtGui, QtCore
import threading
import time
class MyWidget(QtGui.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.computeButton = QtGui.QPushButton("Compute", self)
self.progressBar = QtGui.QProgressBar()
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.computeButton)
layout.addWidget(self.progressBar)
self.computeButton.clicked.connect(self.compute)
self.valueChanged.connect(self.progressBar.setValue)
def compute(self):
nbFiles = 10
self.progressBar.setRange(0, nbFiles)
def inner():
for i in range(1, nbFiles+1):
time.sleep(0.5) # Process Image
self.valueChanged.emit(i) # Notify progress
self.thread = threading.Thread(target = inner)
self.thread.start()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
I have a problem i can't quite figure out for some time. I have a main window application and a QDialog that should pop out after clicking one of the buttons, but the show() method on QDialog seems to be waiting for the funcion connected to the "clicked()" signal to end!
I want the dialog to show right after calling the QDialog.show() method, not after all the other code instructions in that function...
Of course in my code I am going to replace the sleep(5) part with much more complicated code, but this pictures the problem and the code I put there is irrelevant to the issue, i think (database connections and updates)
being more specific:
# -*- coding: utf-8 -*-
import sys
import PyQt4
from PyQt4 import QtCore, QtGui
from twython import Twython, TwythonError
from project import Ui_MainWindow
from time import sleep
import psycopg2, globalvals, updater
import updating, noconnection
class UpWindow(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)
self.ui = updating.Ui_updating()
self.ui.setupUi(self)
class NoConnection(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)
self.ui = noconnection.Ui_noConnection()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.noConnectionClose, QtCore.SIGNAL("clicked()"), self.close)
class MyCounter(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.noConn = NoConnection(self)
self.upWin = UpWindow(self)
QtCore.QObject.connect(self.ui.refreshButton,QtCore.SIGNAL("clicked()"), self.refresh)
QtCore.QObject.connect(self.ui.manageButton,QtCore.SIGNAL("clicked()"), self.manage)
def refresh(self):
self.upWin.show()
self.upWin.show
self.upWin.setVisible(True)
self.setEnabled(False)
self.upWin.setEnabled(True)
#Thats the issue - the sleep instruction is being held
#BEFORE the showing of upWin QDialog
sleep(5)
def manage(self):
print 'ok'
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MyCounter()
myapp.upcontent()
myapp.show()
sys.exit(app.exec_())
Think of the any Qt program as a cooperative-multitasking system. Graphics and events in general are handled by the main loop. You don't want to stay long in any slot, because the library won't process signals (say button clicks, repaints, but also other stuff) in the mean time.
If you want to do some heavy processing, or anything that needs to wait for resources while the rest of the program is chugging along, use a QThread.
Another option is to force the event processing with qApp.processEvents() (you can find qApp in QtGui), just before your sleep(5) (or whatever code you're going to put in place of it).
Edit: Now, keep in mind that forcing the event processing will just show the QDialog you're trying to popup. You can't do anything with it (remember, no event processing) without calling again qApp.processEvents() or returning from the slot.
If MyCounter represents a widget that does a long computation and updates a dialog during that time, then sleep(5) is not representative of it, because during those 5 seconds the GUI can't handle events. For a "long running" function you would move the blocking part to a QThread and either poll the thread or connect to a signal it emits as it progresses, either way you would not hold up the GUI event loop during that time (for example, the polling, which takes very little time, would occur in an idle callback). The simplest way to create your test would be to use a timed callback into your MyCounter:
def refresh(self):
... show stuff, then:
self.timer = QTimer()
self.timer.timeout.connect(self.updateDialog)
timer.start(100) # 10 times per sec
def updateDialog(self):
#get thread status
if self.thread.status != self.oldStatus:
self.upWin.updateStatus( self.thread.status )