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_())
Related
I have a PyQt5 GUI application mainwindow that sets geometry based on the screen size. When I call the toogleLogWindow() function, the visibility property of hLayoutWidget_error changes, but window resize does not happen. When I restore the mainwindow manually by clicking the restore button on the right top corner, the resize function works. Can anyone help me understand this behavior? actionToggleLogWindow status is not checked by default.
import sys, os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUI()
def setupUI(self):
# Set screen size parameters
for i in range(QApplication.desktop().screenCount()):
self.window_size = QApplication.desktop().availableGeometry(i).size()
self.resize(self.window_size)
self.move(QPoint(0, 0))
self._button = QtWidgets.QPushButton(self)
self._button.setText('Test Me')
self._editText = QtWidgets.QComboBox(self)
self._editText.setEditable(True)
self._editText.addItem("")
self._editText.setGeometry(QtCore.QRect(240, 40, 113, 21))
# Connect signal to slot
self._button.clicked.connect(self.toogleLogWindow)
def toogleLogWindow(self):
if self._editText.currentText() == "0":
h = self.window_size.height()
w = int(self.window_size.width()/2)
self.resize(w,h)
elif self._editText.currentText() == "1":
h = self.window_size.height()
w = int(self.window_size.width())
self.resize(w,h)
else:
pass
def get_main_app(argv=[]):
app = QApplication(argv)
win = MainWindow()
win.show()
return app, win
def main():
app, _win = get_main_app(sys.argv)
return app.exec_()
if __name__ == '__main__':
sys.exit(main())
It should be noted that:
It seems that if setting the maximum size of a window before being shown and then displaying it is equivalent to maximizing the window.
When a window is maximized you cannot change its size unless you return it to the previous state, for example if you change the size of the window manually until it is in the normal state then you can just change the size.
So there are several alternatives for this case:
Do not set the full size of the screen:
self.window_size = QApplication.desktop().availableGeometry(i).size() - QSize(10, 10)
Set the size after displaying:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUI()
def setupUI(self):
# Set screen size parameters
for i in range(QApplication.desktop().screenCount()):
self.window_size = QApplication.desktop().availableGeometry(i).size()
self._button = QPushButton(self)
self._button.setText("Test Me")
self._editText = QComboBox(self)
self._editText.setEditable(True)
self._editText.addItem("")
self._editText.setGeometry(QRect(240, 40, 113, 21))
# Connect signal to slot
self._button.clicked.connect(self.toogleLogWindow)
def init_geometry(self):
self.resize(self.window_size)
self.move(QPoint(0, 0))
def toogleLogWindow(self):
if self._editText.currentText() == "0":
h = self.window_size.height()
w = int(self.window_size.width() / 2)
self.resize(w, h)
elif self._editText.currentText() == "1":
h = self.window_size.height()
w = int(self.window_size.width())
self.resize(w, h)
else:
pass
def get_main_app(argv=[]):
app = QApplication(argv)
win = MainWindow()
win.show()
win.init_geometry()
return app, win
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_())
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_())
I have a created a GUI in PyQt5. I have 1 current widget, and others to come soon, that I want to put on my GUI. However, I can't find a way to postion them however I want. I am using things like grid layouts, but they only allow "locked" places i.e. (0,1) (1,0) (1,1), I don't think they allow you to just use things like move() to position the elements.
I have already tried, as mentioned, QGridLayout, QHBoxLayout and QVBoxLayout. I have also tried groups.
import sys
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Settings():
SCENE_SIZE_X = 1200
SCENE_SIZE_Y = 900
GRID_COUNT_X = 4
GRID_COUNT_Y = 5
GRID_BOX_WIDTH = 100
GRID_BOX_HEIGHT = 100
class Grid(QtWidgets.QGraphicsScene):
def __init__(self):
super().__init__()
self.lines = []
self.scrolled = 0
self.initUI()
def initUI(self):
self.draw_grid(Settings.GRID_COUNT_X, Settings.GRID_COUNT_Y, Settings.GRID_BOX_WIDTH, Settings.GRID_BOX_HEIGHT)
self.set_opacity(1.0)
def draw_grid(self, x_count, y_count, x_size, y_size):
width = x_count * x_size
height = y_count * y_size
self.setSceneRect(0, 0, width, height)
self.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex)
pen = QPen(QColor(0,0,0), 1, Qt.SolidLine)
for x in range(0,x_count+1):
xc = x * x_size
self.lines.append(self.addLine(xc,0,xc,height,pen))
self.addText
for y in range(0,y_count+1):
yc = y * y_size
self.lines.append(self.addLine(0,yc,width,yc,pen))
def set_opacity(self,opacity):
for line in self.lines:
line.setOpacity(opacity)
def delete_grid(self):
for line in self.lines:
self.removeItem(line)
del self.lines[:]
def wheelEvent(self,event):
self.scrolled += event.delta()/90
if(self.scrolled > -30.0):
self.delete_grid()
self.draw_grid(Settings.GRID_COUNT_X, Settings.GRID_COUNT_Y, Settings.GRID_BOX_WIDTH + self.scrolled, Settings.GRID_BOX_HEIGHT + self.scrolled)
else:
self.scrolled = -30.0
class App(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 100, Settings.SCENE_SIZE_X, Settings.SCENE_SIZE_Y)
grid = QtWidgets.QGraphicsView(Grid())
layout = QGridLayout(self)
layout.addWidget(grid, 0, 1)
layout.addWidget(QtWidgets.QPushButton("hello!"), 0, 0)
layout.setRowStretch(2, 1)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setStyle("Fusion")
ex = App()
ex.show()
sys.exit(app.exec_())
I am getting the elements to show up, just not where I want them to be located as I am using layouts.
As I mentioned, I did try groups and they sort of worked. I got everything correct but it looked messy as there were widgets inside of groups, inside of groups:
What is the best way to allow me to position them to, maybe, single pixel precision, i.e. using move()?
Thanks,
Dream
EDIT: the code and the image don't line up becuase it was an old iteration of the code
I have a window that has six symmetrically placed labels, all showing images (designed using qt-designer with the help of layouts). I would like to resize these images according to the changing window size. I have found some help in previous questions like: PyQt: Detect resizing in Widget-window resized signal
At present, using resizeEvent() in my case does not shrink the images according to the resize function. It is already triggered with the display of my form window thereby making the pushButton useless. Above all, the resulting execution is very slow. My images are of 2058x1536 dimension and displayed transparently.
My qt-designer code is given here: https://pastebin.com/TzM6qiKZ
import Ui_ImageCrop_Test
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QPixmap, QImage, QPainter, QColor
from PyQt5.QtCore import Qt
class ImageCrop(Ui_ImageCrop_Test.Ui_MainWindow, QMainWindow):
def __init__(self, parent=None):
super(ImageCrop, self).__init__()
self.setupUi(self)
self.transparency = 220
with open("Img_files.txt") as file:
self.img_files = file.read().splitlines()
self.length = len(self.img_files)
self.pushButton_1.clicked.connect(self.click1)
self.label_1.resizeEvent = self.click1
def click1(self, event):
for i in range(6):
image = QImage(self.img_files[i])
image = image.convertToFormat(QImage.Format_ARGB8565_Premultiplied)
p = QPainter(image)
p.setCompositionMode(QPainter.CompositionMode_DestinationIn)
p.fillRect(image.rect(), QColor(0, 0, 0, self.transparency))
p.end()
pixmap = QPixmap(image)
w = int(self.label_1.width() - 4.0)
h = int(self.label_1.height() - 4.0)
smaller_pixmap = pixmap.scaled(w, h, Qt.IgnoreAspectRatio, Qt.FastTransformation)
if i == 0:
self.label_1.setPixmap(smaller_pixmap)
if i == 1:
self.label_2.setPixmap(smaller_pixmap)
if i == 2:
self.label_3.setPixmap(smaller_pixmap)
if i == 3:
self.label_4.setPixmap(smaller_pixmap)
if i == 4:
self.label_5.setPixmap(smaller_pixmap)
if i == 5:
self.label_6.setPixmap(smaller_pixmap)
def main():
app = QApplication(sys.argv)
form1 = ImageCrop()
form1.show()
app.exec_()
if __name__ == '__main__': main()
Is there any solution to run this code faster? For example, I was thinking to make all my labels turn blank during a mouse click at the edge of my window and then images reappear after the mouse button is released. This does not seem so neat. Also, I am not sure if using paintEvent can reduce my lag. Thank you for your suggestions and comments.
QLabel has the scaledContents property that allows the image to scale automatically:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import Ui_ImageCrop_Test
class ImageCrop(QtWidgets.QMainWindow, Ui_ImageCrop_Test.Ui_MainWindow):
def __init__(self, parent=None):
super(ImageCrop, self).__init__()
self.setupUi(self)
self.pushButton_1.clicked.connect(self.click1)
self.transparency = 220
with open("Img_files.txt") as file:
self.img_files = file.read().splitlines()
#QtCore.pyqtSlot()
def click1(self):
labels = [self.label_1, self.label_2, self.label_3,
self.label_4, self.label_5, self.label_6]
for label, filename in zip(labels, self.img_files):
image = QtGui.QImage(filename)
image = image.convertToFormat(QtGui.QImage.Format_ARGB8565_Premultiplied)
p = QtGui.QPainter(image)
p.setCompositionMode(QtGui.QPainter.CompositionMode_DestinationIn)
p.fillRect(image.rect(), QtGui.QColor(0, 0, 0, self.transparency))
p.end()
pixmap = QtGui.QPixmap(image)
w = int(label.width() - 4.0)
h = int(label.height() - 4.0)
smaller_pixmap = pixmap.scaled(w, h, QtCore.Qt.IgnoreAspectRatio, QtCore.Qt.FastTransformation)
label.setPixmap(smaller_pixmap)
label.setScaledContents(True)
def main():
app = QtWidgets.QApplication(sys.argv)
form1 = ImageCrop()
form1.show()
app.exec_()
if __name__ == '__main__': main()