Python3, PyQt5: QProgressBar updating makes actual task very slow - python

I am using Python 3.5, PyQt5 on OSX and I was wondering if there was a possibility to update the QProgressBar without slowing down the whole computing work.
Here was my code and if I did just the task without the progressbar update it was soo much faster!!
from PyQt5.QtWidgets import (QWidget, QProgressBar, QPushButton, QApplication)
from jellyfish import levenshtein_distance, jaro_winkler
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.pbar = QProgressBar(self)
self.pbar.setGeometry(30, 40, 200, 25)
self.btn = QPushButton('Start', self)
self.btn.move(40, 80)
self.btn.clicked.connect(self.doAction)
self.setGeometry(300, 300, 280, 170)
self.show()
def doAction(self):
#setup variables
step = 0
m = 1000
n = 500
step_val = 100 / (m * n)
#make task
for i in range(m):
for j in range(n):
jaro_winkler(str(i), str(j))
#show task
print(i,j)
#update progressbar
step += step_val
self.pbar.setValue(step)
QApplication.processEvents()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Then with help from stackoverflow users I got the hint to make a separate working thread and connect the updating signal to the GUI. I did it and it looks now like the following code. It also works and is much faster, but I can't figure out how to connect the emited signal to the GUI. Can somebody please help me? Many thanks in advance!
from jellyfish import jaro_winkler
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QProgressBar, QMainWindow
import time
import numpy as np
class Main_Window(QMainWindow):
def __init__(self):
super(Main_Window,self).__init__()
self.initUI()
def initUI(self):
self.pbar = QProgressBar(self)
self.pbar.setGeometry(30, 40, 200, 25)
self.btn = QPushButton('Start', self)
self.btn.move(40, 80)
self.btn.clicked.connect(MyThread.doAction)
self.setGeometry(300, 300, 280, 170)
self.show()
def updateProgressBar(self, val):
self.pbar.setValue.connect(val)
class MySignal(QWidget):
pbar_signal = QtCore.pyqtSignal(int)
class MyThread(QtCore.QThread):
def __init__(self):
super().__init__()
def doAction(self):
t = time.time() #for time measurement
#setup variables
step = 0
m = 1000
n = 500
pbar_val = 100 / m
signal_instance = MySignal()
#make task
for i in range(m):
for j in range(n):
jaro_winkler(str(i), str(j))
signal_instance.pbar_signal.emit(pbar_val)
#measuring task time
print(np.round_(time.time() - t, 3), 'sec elapsed')
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Main_Window()
sys.exit(app.exec_())

There are three things slowing the code down:
Printing to stdout is very expensive - especially when you do it 500,000 times! On my system, commenting out print(i,j) roughly halves the time doAction takes to run.
It's also quite expensive to call processEvents 500,000 times. Commenting out QApplication.processEvents() reduces the run time by another two-thirds.
Commenting out self.pbar.setValue(step) halves the time again.
Hopefully it should be obvious by now that attempting to update the gui 500,000 times during a task that should take less than a second is massive overkill! Most user's reaction times are about 200ms at best, so you only need to update the gui about once every 100ms.
Given that, one simple fix is to move the updates into the outer loop:
for i in range(m):
for j in range(n):
jaro_winkler(str(i), str(j))
# show task
# print(i,j)
step += step_val
# update progressbar
self.pbar.setValue(step)
QApplication.processEvents()
But an even better solution would be to move the calculations into a separate worker thread and have it periodically emit a custom signal to update the progress bar:
class Main_Window(QMainWindow):
...
def initUI(self):
...
self.btn.clicked.connect(self.doAction)
self.thread = MyThread()
self.thread.pbar_signal.connect(self.pbar.setValue)
def doAction(self):
if not self.thread.isRunning():
self.thread.start()
class MyThread(QtCore.QThread):
pbar_signal = QtCore.pyqtSignal(int)
def run(self):
#for time measurement
t = time.time()
#setup variables
m = 1000
n = 500
progress = step = 100 / m
#make task
for i in range(m):
for j in range(n):
jaro_winkler(str(i), str(j))
progress += step
self.pbar_signal.emit(progress)
#measuring task time
print(np.round_(time.time() - t, 3), 'sec elapsed')

Related

Move GIF while python pool process is executing in parallel with pyqt5

I want to update the GIF while pool process are executing in parallel. Now GIF is getting stuck, it is responding with respect to progressbar value update. I want GIF to execute independently. I have tried using it thread but that is not solution for my process. Once control is going to pool-process it getting GUI freeze, not responding. I have given high level piece of code that in this GIF should not stuck. I am copying number of files through process in the original code there it will take much time to complete the task till then GUI gets not responding. So pool-process I can not remove or replace. Let me know if this GIF continuous movement is possible irrespective of progressbar and process executing. I would appreciate code changes because i am new to python and PYQT5.
Thanks in advance.
from PyQt5.QtGui import QMovie
import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton, QVBoxLayout, QLabel)
from multiprocessing import Pool, freeze_support
TIME_LIMIT = 100
def func(a, b):
print(a +b)
class External (QThread):
countChanged = pyqtSignal(int)
def run(self):
count = 0
print("check1")
while count < TIME_LIMIT:
count += 1
time.sleep(1)
print("thread1 update")
self.countChanged.emit(count)
class Actions (QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.setGeometry(200,300,200,200)
col = QVBoxLayout(self)
self.text = ['ON', 'OFF']
self.label = QLabel(self)
self.movie = QMovie("movie.gif")
self.label.setMovie(self.movie)
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
col.addWidget(self.label)
col.addWidget(self.progress)
col.addWidget(self.button)
self.label.move(0,90)
self.button.move(0, 30)
self.button.clicked.connect(self.onButtonClick)
self.show()
def startAnimation(self):
self.movie.start()
def onButtonClick(self):
self.startAnimation()
self.calc = External()
a_args = [1, 2, 3, 4, 5, 6]
second_arg = 1
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
QApplication.processEvents()
for i in a_args:
pool = Pool(processes=3)
M = pool.starmap(func, zip(a_args, repeat(second_arg)))
QApplication.processEvents()
pool.close()
pool.join()
print("value of args",i)
def onCountChanged(self, value):
self.progress.setValue(value)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())

Python PyQt5 load Image for QPushButton on QThread

So I have this UI that loads ALOT of buttons, all with Images, problem is, it takes WAY to long, so I tried putting it into a QThread, I had it working, but there was no speed difference, so I tried a different solution, but now the Thread wont start.
Code:
import sys
import os
from PyQt5 import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtPrintSupport, QtWidgets, uic
# I've tried using a QThread for this, but it still took the same amount of time.
class LoadImageThumbnailshread(QThread):
def __init__(self, listButtons, listPaths):
QThread.__init__(self)
self.listButtons = listButtons
self.listPaths = listPaths
def run(self):
self.process_images_Thread()
def process_images_Thread(self):
for i, j in enumerate(self.listButtons):
j.setIcon(QIcon(self.listPaths[i]))
j.setIconSize(QSize(150-6, 60-6))
class App(QDialog):
def __init__(self):
super().__init__()
self.title = 'PyQt5 layout - pythonspot.com'
self.left = 10
self.top = 10
self.width = 320
self.height = 100
self.images_path = []
self.button_images = []
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.createGridLayout()
windowLayout = QVBoxLayout()
windowLayout.addWidget(self.horizontalGroupBox)
self.setLayout(windowLayout)
self.show()
def createGridLayout(self):
self.horizontalGroupBox = QGroupBox("Grid")
layout = QGridLayout()
layout.setColumnStretch(1, 4)
layout.setColumnStretch(2, 4)
for i in range(100):
self.btnImage = QPushButton()
self.btnImage.setObjectName('btnImage')
self.images_path.append(os.path.dirname(
os.path.abspath(__file__)) + 'view.png')
# ! I have also tried using Pixmaps with Clickable labels, but it made no diffrence.
# pixmap = QPixmap(os.path.dirname(os.path.abspath(__file__)) + image_locations[i])
# pixmap = pixmap.scaled(60, 300, Qt.KeepAspectRatio, Qt.FastTransformation)
# self.btnImage.setPixmap(pixmap)
# ! Enableing this loads the images, but very slowly
# self.btnImage.setIcon(QIcon(os.path.dirname(os.path.abspath(__file__)) + '/view.png'))
# self.btnImage.setIconSize(QSize(150-6, 60-6))
self.button_images.append(self.btnImage)
layout.addWidget(self.btnImage, i, 0)
# ! This starts the QThread
converter = LoadImageThumbnailshread(
self.button_images, self.images_path)
converter.start()
self.horizontalGroupBox.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
So to explain more of what I want, I want to start a QThread AFTER the UI has loaded, that QThread will load all of the Images onto each of the buttons. Problem is: it doesn't work right now.
Another problem that I had, is that when I got the QThread the UI waited for it to finish, and then everything all of a sudden popped up. I want the QThread to be completely separate and pretty, much see all images loading one by one if you will.
The threads do not serve to accelerate any task!!! but to execute tasks that do not block any thread. Given the above, if I have n "task" and each one is executed in "n" threads then the total execution time will be 1 task, that is, no task is accelerated but the tasks are redistributed. In conclusion: If you want to speed up a task then threads is not a default option as it depends on the application.
On the other hand, the loading of the image does not consume time but there are many tasks that consume little time that in total is equivalent to a task that consumes a lot of time and unfortunately that task cannot be executed in another thread or process since the elements of the GUI are not thread-safe.
A workaround is that the load is of a small group of n elements every T seconds so the total task will be distributed and the user will not observe any effect of lag.
import os
import sys
from PyQt5.QtCore import QSize, QTimer
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
QApplication,
QDialog,
QGridLayout,
QGroupBox,
QPushButton,
QVBoxLayout,
)
class App(QDialog):
def __init__(self):
super().__init__()
self.title = "PyQt5 layout - pythonspot.com"
self.left = 10
self.top = 10
self.width = 320
self.height = 100
self.images_path = []
self.button_images = []
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.createGridLayout()
windowLayout = QVBoxLayout(self)
windowLayout.addWidget(self.horizontalGroupBox)
self._iter = iter(range(100))
self._timer = QTimer(interval=10, timeout=self.lazy_loading)
self._timer.start()
def createGridLayout(self):
self.horizontalGroupBox = QGroupBox("Grid")
self.grid_layout = QGridLayout()
self.grid_layout.setColumnStretch(1, 4)
self.grid_layout.setColumnStretch(2, 4)
self.horizontalGroupBox.setLayout(self.grid_layout)
def lazy_loading(self):
try:
i = next(self._iter)
except StopIteration:
self._timer.stop()
else:
btn = QPushButton()
image_path = os.path.join(os.path.abspath(__file__), "view.png")
btn.setIcon(QIcon(image_path))
btn.setIconSize(QSize(150 - 6, 60 - 6))
self.grid_layout.addWidget(btn, i, 0)
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())

How to update and refresh in PyQt5 at a higher frequency

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class MainWindow(QWidget):
signal = pyqtSignal()
def __init__(self):
super(MainWindow, self).__init__()
self.buttonList = list()
self.counter = 0
self.setUI()
self.signal.connect(self.updatebutton)
self.startTimer(10)
def timerEvent(self, *args, **kwargs):
self.signal.emit()
def updatebutton(self):
for button in self.buttonList: # type: QLabel or QPushButton
button.setText(str(self.counter))
self.counter += 1
def setUI(self):
gridLayout = QGridLayout()
for i in range(10):
for j in range(10):
button = QLabel(self)#QPushButton(self)
self.buttonList.append(button)
gridLayout.addWidget(button, i, j)
self.setLayout(gridLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
When I set the timer at 10 ms, all Labels can increase its value by 100 per second. However, when I change the timer to 1 ms, all Labels can not increase its value by 1000 per second.
When I use Pushbutton instead of Label, even the timer is set to 100 ms, all buttons can not increase their value by 100 per second. But when I reduce the number of buttons, e.g., totally 10 buttons instead of 100, these buttons can work well.
3.I meet with this problem recently when I used a data acquisition software called DEWESOFT, which can produce many subwidget or subwindow(very similar to QMidArea and QMidSubwindow) to draw curves and display values of high-frequency signals. Hence, I'd like to find out how and why. I wonder how to control the refresh rate of widget and how to determine the limit of UI refresh ability. If I wanna refresh more widgets at a high frequency, what should I do?
Later I tried Thread to improve, it seems to work a litter better, but I don't know why.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import threading
import sys
import time
class MainWindow(QWidget):
signal = pyqtSignal()
def __init__(self):
super(MainWindow, self).__init__()
self.buttonList = list()
self.counter = 0
self.setUI()
self.signal.connect(self.updatebutton)
self.thread = threading.Thread(target=self.timerevent, args=())
self.thread.setDaemon(True)
self.thread.start()
# self.startTimer(10)
def timerEvent(self, *args, **kwargs):
self.signal.emit()
def timerevent(self):
while True:
self.signal.emit()
time.sleep(0.01)
def updatebutton(self):
print(self.counter)
for button in self.buttonList: # type: QPushButton
button.setText(str(self.counter))
self.counter += 1
def setUI(self):
gridLayout = QGridLayout()
for i in range(10):
for j in range(10):
button = QPushButton(self)
self.buttonList.append(button)
gridLayout.addWidget(button, i, j)
self.setLayout(gridLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

How to get the current Value of ProgressBar in pyQt5?

I'm Learning Pyqt5.
The problem is that i want to get the value of the progressbar in pyqt5
i tried using self.progressBar.getValue() or self.progressBar.getInt() None of them worked
The actual code is a little big.but never mind. Please help
i just need the syntax for getting the current Value from the progressbar ie: between 1 to 100
According to their documentation, the method for getting the value is just value(), so in your case, it would be self.progressBar.value()
I agree with #dustin-we, here is a minimal code example:
import sys
import time
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
TIME_LIMIT = 100
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in the start of a timer and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
count = 0
while count < TIME_LIMIT:
count += 1
time.sleep(0.01)
self.progress.setValue(count)
# !! Here is the command you need !!
print(self.progress.value())
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())

Window updates only when it resizes in PyQt5

The application should display an image from a grid of pixels. The color of all pixels should change 30 times per second. After starting the app works for a few seconds and after that the pixels updates will stop. When window resized the pixels updating resumes. With a long-term update of the pixel network, the CPU consumption increases greatly. I tested it on Windows and there the pixel update stops almost immediately. Used the Threading library, and the PyQt5 library to display the interface. How can I make a stable pixels updates in grid?
Here is my code:
from random import choice, randint
from sys import argv
from threading import Thread
from time import sleep
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QIcon, QPalette
from PyQt5.QtWidgets import (QApplication, QFrame, QGridLayout, QMainWindow,
QMenu, QToolBar, QWidget)
class EmulatorWindow(QMainWindow):
spacing = None
app_running = True
def __init__(self, spacing=1, screen_resolution=(16, 16)):
super().__init__()
self.spacing = spacing
# Pixel Grid
self.grid = QGridLayout()
self.grid.setContentsMargins(0, 0, 0, 0)
self.grid.setSpacing(self.spacing)
for x in range(0, screen_resolution[0]):
for y in range(0, screen_resolution[1]):
pixel = QWidget()
pixel.setAutoFillBackground(True)
self.grid.addWidget(pixel, y, x)
# Application thread
self.applicationThread = Thread(target=self.applicationRunner, args=())
self.applicationThread.start()
# Window Properties
self.setGeometry(300, 300, 450, 495)
self.setWindowTitle('Pixels Grid')
widget = QWidget()
widget.setLayout(self.grid)
self.setCentralWidget(widget)
self.setMinimumSize(QSize(450, 495))
self.show()
def applicationRunner(self):
color = 0
while True:
if self.app_running == False:
break
for x in range(0, 16):
for y in range(0, 16):
self.grid.itemAtPosition(x, y).widget().setPalette(QPalette([Qt.red, Qt.blue, Qt.green][color]))
sleep(1 / 30)
color = color + 1
if color == 3:
color = 0
def switchSpacing(self):
self.grid.setSpacing(self.spacing if self.grid.spacing() == 0 else 0)
if __name__ == '__main__':
app = QApplication(argv)
ex = EmulatorWindow()
app.exec_()
ex.app_running = False
Activity Monitor Screenshot
In the screenshot is MenuBar and ToolBar, but, they do not affect the problem
Application Screenshot
The reason why the GUI is not updated with the thread is that Qt prohibits the updating of graphic elements from another thread, for more information read GUI Thread and Worker Thread. A thread should not be used if the task is not heavy, for example if we test when it consumes changing a color using the following code:
t = QtCore.QElapsedTimer()
t.start()
pal = QtGui.QPalette([QtCore.Qt.red, QtCore.Qt.blue, QtCore.Qt.green][self._color])
for x in range(self.grid.rowCount()):
for y in range(self.grid.columnCount()):
w = self.grid.itemAtPosition(x, y).widget()
if w is not None:
w.setPalette(pal)
self._color = (self._color +1) % 3
print(t.elapsed(), " milliseconds")
Obtaining the following results:
4 milliseconds
2 milliseconds
2 milliseconds
3 milliseconds
2 milliseconds
3 milliseconds
3 milliseconds
2 milliseconds
3 milliseconds
# ...
Supporting my statement that it is not a heavy task so in this case you should use a QTimer that allows you to do periodic tasks:
from PyQt5 import QtCore, QtGui, QtWidgets
class EmulatorWindow(QtWidgets.QMainWindow):
def __init__(self, spacing=1, screen_resolution=(16, 16)):
super().__init__()
self.spacing = spacing
# Pixel Grid
self.grid = QtWidgets.QGridLayout()
self.grid.setContentsMargins(0, 0, 0, 0)
self.grid.setSpacing(self.spacing)
for x in range(screen_resolution[0]):
for y in range(screen_resolution[1]):
pixel = QtWidgets.QWidget(autoFillBackground=True)
self.grid.addWidget(pixel, y, x)
# Window Properties
self.setGeometry(300, 300, 450, 495)
self.setWindowTitle('Pixels Grid')
widget = QtWidgets.QWidget()
self.setCentralWidget(widget)
widget.setLayout(self.grid)
self.setMinimumSize(QtCore.QSize(450, 495))
self._color = 0
timer = QtCore.QTimer(self, interval=1000/30, timeout=self.applicationRunner)
timer.start()
#QtCore.pyqtSlot()
def applicationRunner(self):
pal = QtGui.QPalette([QtCore.Qt.red, QtCore.Qt.blue, QtCore.Qt.green][self._color])
for x in range(self.grid.rowCount()):
for y in range(self.grid.columnCount()):
w = self.grid.itemAtPosition(x, y).widget()
if w is not None:
w.setPalette(pal)
self._color = (self._color +1) % 3
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
ex = EmulatorWindow()
ex.show()
sys.exit(app.exec_())

Categories