I am having a strange issue with PyQt5 in Python 3.5.
I have two classes, FrontEnd(QWidget)
and TimerThread(Thread)
. I have a number of QLabel
s defined in the init function of FrontEnd
, all of which work properly.
Shown are the relevant few functions of FrontEnd
:
def update_ui(self):
ret, frame = self.cam_capture.read()
if self.results_pending:
if not path.isfile('output.jpg'):
self.results_pending = False
with open('.out') as content_file:
content = content_file.readlines()[2:-2]
system('rm .out')
self.handle_image_classification(content)
if self.take_picture:
cv2.imwrite('output.jpg', frame)
self.user_prompt.setText('Please wait...')
system('./classifyimage.py --mean mean.binaryproto --nogpu --labels labels.txt model.caffemodel deploy.prototxt output.jpg > .out && rm output.jpg')
self.take_picture = False
self.results_pending = True
image = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888).rgbSwapped()
pix = QPixmap.fromImage(image)
self.video_frame.setPixmap(pix)
def update_bar_graph(self, data):
palette = QPalette()
palette.setColor(QPalette.Background, Qt.white)
for i in range(0, 8):
self.bar_graph_labels[i].setText(str(data[i]) + "%")
height = int(data[i] * 5)
self.bar_graph[i].setFixedSize(self.bar_width, height)
self.bar_graph[i].move(1280 + (i * (self.bar_width + self.bar_spacing)), 640 - height)
def handle_image_classification(self, raw_output):
data = [None] * 8
for i in range(0, len(raw_output)):
raw_output[i] = raw_output[i].strip()
data[int(raw_output[i][-2]) - 1] = float(raw_output[i][:-10])
self.update_bar_graph(data)
And the entire TimerThread class:
class TimerThread(Thread):
front_end = None
def __init__(self, event):
Thread.__init__(self)
self.stopped = event
def run(self):
while not self.stopped.wait(0.02):
FrontEnd.update_ui(self.front_end)
(The front_end
element of TimerThread
is set on init of FrontEnd
)
The problem is in the update_bar_graph
function. When the setFixedSize
call is commented out, the program runs fine, although without properly displaying the bars of the bar graph in my application (which are QLabels
). The move
function seems to run properly. However, the setFixedSize
call causes this error:
QObject::startTimer: Timers cannot be started from another thread
QObject::startTimer: Timers cannot be started from another thread
QObject::killTimer: Timers cannot be stopped from another thread
QObject::startTimer: Timers cannot be started from another thread
I have absolutely no idea why this occurs, and why the move
function, seemingly similar in nature, works fine. Any help would be much appreciated.
(If I should be using a different sort of timer class or different methods for drawing large rectangles in PyQt, I am open to any such suggestions).
EDIT:
This is some weird stuff. I ran it twice the next day, with no changes to the code. (I think...) One time the bar graphs did not display but no errors were thrown. The other time I got this:
7fdfaf931000-7fdfaf932000 r--p 0007a000 08:07 655633 /usr/lib/x86_64-linux-gnu/libQt5DBus.so.5.5.1
7fdfaf932000-7fdfaf933000 rw-p 0007b000 08:07 655633 /usr/lib/x86_64-linux-gnu/libQt5DBus.so.5.5.1
7fdfaf933000-7fdfaf934000 rw-p 00000000 00:00 0
7fdfaf934000-7fdfaf971000 r-xp 00000000 08:07 667112 /usr/lib/x86_64-linux-gnu/libxkbcommon.so.0.0.0
7fdfaf971000-7fdfafb70000 ---p 0003d000 08:07 667112 /usr/lib/x86_64-linux-gnu/libxkbcommon.so.0.0.0
7fdfafb70000-7fdfafb72000 r--p 0003c000 08:07 667112 /usr/lib/x86_64-linux-gnu/libxkbcommon.so.0.0.0
7fdfafb72000-7fdfafb73000 rw-p 0003e000 08:07 667112 /usr/lib/x86_64-linux-gnu/libxkbcommon.so.0.0.0
7fdfafb73000-7fdfafb7a000 r-xp 00000000 08:07 667110 /usr/lib/x86_64-linux-gnu/libxkbcommon-x11.so.0.0.0
7fdfafb7a000-7fdfafd79000 ---p 00007000 08:07 667110 /usr/lib/x86_64-linux-gnu/libxkbcommon-x11.so.0.0.0
I think I may have found a bug in PyQt5.
As mentioned by @mata your code is not thread safe. This is likely where the errant behaviour is coming from, and should certainly be fixed before debugging further (this is related).
The reason it is thread unsafe is because you are interacting with GUI objects from the secondary thread directly. You should instead emit a signal from your thread to a slot in the main thread where you can safely update your GUI. This however requires you use a QThread
which is recommended anyway as per this post.
This requires the following changes:
class TimerThread(QThread):
update = pyqtSignal()
def __init__(self, event):
QThread.__init__(self)
self.stopped = event
def run(self):
while not self.stopped.wait(0.02):
self.update.emit()
class FrontEnd(QWidget):
def __init__(self):
super().__init__()
... # code as in your original
stop_flag = Event()
self.timer_thread = TimerThread(stop_flag)
self.timer_thread.update.connect(self.update_ui)
self.timer_thread.start()
I've also modified your code so that it stores a reference to the TimerThread
in the FrontEnd
object so the thread is not garbage collected.
I would also add that this is an overly complex way of triggering an update every 0.02 seconds. You could use a QTimer
to just call the update_ui
method and ditch threads entirely but I'm taking the approach that you will likely want to do something more complex with your threads later, so have demonstrated how to do it safely!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With