I am creating an app using Python 3.10 and PyQT6. It is going to be a photo editor. The idea is that user presses the button #1 to choose an image file, then the image is shown in window (in a pixmap), and when a user presses the button #2, program draws something on image with OpenCV and replaces the image with a new one that is shown instead of the previous one.
This happened as expected (one image was replaced by another) until I put the drawing and replacing images function (actually the button #2 on-click connected function) in a thread. After that the previous image disappears and the new one doesn't appear.
The situation gets more complex due to the fact I am using a custom Pixmap Container as was adviced in a StackOverflow answer
I have tried searching the Net but failed to find a solution. I have tried experimenting with QtCore.QTimer.singleShot()
to force the layout to get some update but I got confused were to place this function. I had a thought that deleteLater()
deletes the new image but that was not the case. I have done debug and found that the problem appears in the line self.centralWidget().layout().replaceWidget(self.image, image)
(see the code below). I have tried to find another way to replace a pixmap image but all I found was greatly close to my code.
Now I am going to give you a minimal, reproducible example of code I had written.
Custom PixmapContainer class
class PixmapContainer(QLabel):
def __init__(self, pixmap, parent=None):
super(PixmapContainer, self).__init__(parent)
self._pixmap = QPixmap(pixmap)
self.setMinimumSize(256, 256) # needed to be able to scale down the image
def resizeEvent(self, event):
w = min(self.width(), self._pixmap.width())
h = min(self.height(), self._pixmap.height())
self.setPixmap(self._pixmap.scaled(w, h, Qt.AspectRatioMode.KeepAspectRatio))
Main Window class (that is a lot of code so I made comments to emphasise the problem)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# initializing image
self.imgpath = None
self.image = QLabel()
# button 1 - Choose photo
button_img = QPushButton("Choose photo")
button_img.clicked.connect(self.openfile)
# button 2 - Start-some-work
button_recgn = QPushButton("Start-some-work")
button_recgn.clicked.connect(self.start_recogn_thread) # THIS DOES NOT WORK
# button_recgn.clicked.connect(self.recognize) # THIS WORKS
# adding image and buttons to layout
layout = QVBoxLayout()
layout.addWidget(self.image, stretch=1)
layout.addWidget(button_img)
layout.addWidget(button_recgn)
wid = QWidget()
wid.setLayout(layout)
self.setCentralWidget(wid)
# editing and replacing images itself
def recognize(self):
# set the widgets to disabled while editing (doesn't do anything without thread)
self.centralWidget().setDisabled(True)
img_data = cv2.imdecode(np.fromfile(self.imgpath, dtype=np.uint8), cv2.IMREAD_UNCHANGED)
img_data = cv2.cvtColor(img_data, cv2.COLOR_BGR2RGB)
cv2.rectangle(img_data, (10, 10), (100, 100), (255, 0, 0), 2) # some drawing
h, w, c = img_data.shape
image = PixmapContainer(QImage(img_data.data, w, h, w * 3, QImage.Format.Format_RGB888))
# ------------------------- THE PROBLEM HERE!!! --------------------------------
self.centralWidget().layout().replaceWidget(self.image, image)
# -------------------------------^^^^^^^^---------------------------------------
# ----------- Everything except this line works properly in thread -------------
# deleting previous image
self.image.deleteLater()
self.image = image
self.centralWidget().setDisabled(False)
# editing and replacing images in thread
def start_recogn_thread(self):
th = threading.Thread(target=self.recognize) # THREAD
th.start()
# open image from file
def openfile(self):
fname = QFileDialog.getOpenFileName(
self,
'Choose photo',
'.',
"Supported files (*.png;*.jpg;*.bmp);;PNG Files (*.png);;JPG Files (*.jpg);;BMP File (*.bmp)"
)[0]
# saving image path
self.imgpath = fname
# showing image
image = PixmapContainer(fname)
image.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.centralWidget().layout().replaceWidget(self.image, image)
self.image.deleteLater() # WORKS!
self.image = image
Application
app = QApplication([])
window = MainWindow()
window.show()
app.exec()
Imports
from PyQt6.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton,\
QFileDialog, QVBoxLayout, QLabel
from PyQt6.QtGui import QPixmap, QImage
from PyQt6.QtCore import Qt
import threading
import cv2
import numpy as np
I am very new to PyQT and I am very sorry to take your time on figuring out this problem.