QPixmap not safe outside GUI thread - python

My program has the main GUI thread to handle the user interface.
Another thread is started to handle the hard work (loops, calculations, etc) without freezing the main GUI.
From my "calculations thread" I am using another module draw_plots which only draws and saves many kinds of plots.
import draw_plots as plots
class calculatorThread(QtCore.QThread):
signal1 = QtCore.pyqtSignal(int, int)
signal2 = QtCore.pyqtSignal(int,int)
signal3 = QtCore.pyqtSignal(int)
def __init__(self,input_file_txt, parameter_file_txt):
QtCore.QThread.__init__(self)
#and etc etc
At some point of this thread I am calling:
plots.stacked_barplot(*arguments)
Everything works fine, however, I get a message on the screen many many times:
QPixmap: It is not safe to use pixmaps outside the GUI thread
Would like to know what am I doing wrong and how to avoid this message.

Well, you issue the plot command from the calculator thread, which in turn uses a QPixmap to draw the plot - all from inside your calculator thread.
Ideally, you shouldn't draw from the calculator thread, but e.g. emit a signal that you're ready for plotting - and do the plot in the main thread. Maybe along the following lines:
class calculatorThread(QtCore.QThread):
plot_emit = QtCore.pyqtSignal()
def run(self):
self.plot_args = your_calculation()
self.plot_ready.emit()
Outside, connect the plot_ready signal to your plot command:
calculator.plot_emit.connect(lambda x:
plots.stacked_barplot(*calculator.plot_args))

You should use a QImage if the computation is done in a separate thread. You can also safely access QImage.bits() and do your image processing directly working on pixel data (a lot faster).
As the warning is stating clearly, QPixmap objects should be used only from the GUI thread. You can convert the computed QImage into a QPixmap if you really need a QPixmap (but you can for example draw QImages directly on a QPainter).

Related

Python Tkinter: How to use update() thread safe instead of using mainloop()?

For context why I want to use update() and update_idletask() instead of just using mainloop():
I need to display a cv2.imshow fullscreen FullHD 50 FPS stream, grabbed in a different thread from a Basler Dart USB camera, with a (Tkinter) GUI window topmost .
The conversion from the cv2 Mat to a tk PhotoImage and updating the canvas or label displaying the image is taking over 30ms under best conditions, while grabbing the frame and performing some bitwise operations to overlay an transparent image only takes a few ms. Displaying the stream this way is to slow.
For the sake of not needing to learn another gui framework right now and reusing most of the existing code I found a solution to displaying a cv2.imshow() fullscreen window and simultaneously a tk.Tk window topmost, but I am unsure if this is a good idea and how to implement it the right way, because of the warning in cpython _tkinter.c about the thread lock situation.
I read some suggested solutions for displaying a tk and cv2 window at the same time by using threading, but those didn't work for me, maybe because the image grabbing within my CV2Window is already in a thread.
Just calling update() within the cv2 loop works easy, but I don't know if this is a good idea:
Would it be safe not caring about the tcl lock and just using update(), if I implement the communication between the two windows with a threadsafe queue and nothing within the tkinter events blocks too long?
My simplified code right now is:
# Standard library imports
from cv2 import waitKey
from sys import exit
# Local application imports
from CV2Window import CV2Window
from tkWindows import MenuWindow, MenuFrame
# child class of tk.Tk
# -topmost and overrideredirect True, geometry "+0+0"
class MenuApp(MenuWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# frame withdraws and deiconifies buttons when the menu toggle button is clicked,
# to create a dropdown-menu. Row 0 is used by the toggle button
self.menu_frame = MenuFrame(self)
self.menu_frame.add_button("Example Button", command=None, icon="file-plus", row=1)
self.menu_frame.add_button("Close", command=self.close, icon="power", row=2)
self.menu_frame.grid()
def close(self):
self.destroy()
def main():
cv2window = CV2Window()
gui = MenuApp()
while True:
## Update cv2 window
cv2window.update()
## Update GUI manually
gui.update()
## Check if GUI is still open, otherwise close cv2 window
try:
gui.winfo_exists()
except:
print("GUI closed, closing cv2 window..")
cv2window.close()
break
But the CV2Window contains the camera grabbing with MyCameraHandler, which is a class version of the Basler pypylon example "grabusinggrabloopthread.py" and is aquiring the frames in a different thread.
Simplified code for CV2Window:
# Standard library imports
from cv2 import namedWindow, setWindowProperty, imshow, WINDOW_FULLSCREEN, WND_PROP_FULLSCREEN, waitKey, destroyAllWindows
from pypylon.genicam import GenericException
# Local application imports
from CameraClass import MyCameraHandler
class CV2Window():
def __init__(self):
try:
self.cam_handler = MyCameraHandler()
self.cam_handler.start_grabbing()
except GenericException as e:
print("Exception in CV2Window: ", e)
try:
self.cam_handler.stop()
except:
pass
exit(1)
self.img = self.cam_handler.get_image()
namedWindow("cam", WND_PROP_FULLSCREEN)
setWindowProperty("cam", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN)
imshow("cam", self.img)
def update(self):
self.img = self.cam_handler.get_image()
waitKey(0)
imshow("cam", self.img)
def close(self):
destroyAllWindows()
self.cam_handler.stop()
By reading the cpython _tkinter.c code this could be a problem:
The threading situation is complicated. Tcl is not thread-safe, except
when configured with --enable-threads.
So we need to use a lock around all uses of Tcl. Previously, the
Python interpreter lock was used for this. However, this causes
problems when other Python threads need to run while Tcl is blocked
waiting for events.
If mainloop() is used, Tkinter will get and release the right locks at the right time, but then it is not possible to display and update my CV2Window. From my understanding, update() is only a single tcl call, without any lock management.
As of right now, it is working, but there is zero communication between the two windows.
I need to be able to invoke methods in CV2Window from the Tkinter GUI, and probably in the future also sharing small data/information from the CV2Window to Tkinter.
The next thing I'll try is communicating with a queue, since I don't need to share the image, only some information or actions to perform, and the queue.Queue is threadsafe... this should work, I think?
As long as the events performed because of the update call are taking less than ~15ms, I should be fine and get my needed frametime of <20ms together with the frame grab and imshow, right?
Am I missing something? I am quite new to Python and Tkinter, and wrapping my head around the tcl stuff invoked by Tkinter isn't that easy for me, so any help would be greatly appreciated.
If you are on Unix, the winfo_id() method of any Tkinter widget will return the XID of the underlying drawing surface. You can use that handle to get some other code to draw on that surface. You are recommended to use a Frame with the background set to either None or the empty string (not quite sure which works!) so that Tkinter won't draw on it at the same time; no other widget class supports that. (I know I'd use an empty string in Tk for that effect, which gets converted into a NULL in the underlying structure and that triggers the frame painting code to do nothing on a redraw/repaint request.) I don't know how you'd get the XID into the other framework; it would be responsible for opening its own connection and setting up listeners, but that would work very well in another thread (or even another process; I've done that in the past). That solution does not work on other platforms, as I believe they use IDs that do not have meaning outside of the toolkit implementation. (Technically, there are a few options in Tk that Tkinter does not expose that would help, possibly, but I'm not sure. The -container/-use protocol is not well documented.)
More generally, a custom image type (pretty much requires writing C or C++ code) would let you do something close, though the whole image system is not really designed for animated streaming media. (It certainly isn't designed for hardware acceleration!) It's not something for a quick fix, and I've never tried doing that myself.

CPython extension using omp freezes Qt UI

I am working on a scientific algorithm (image processing), which is written in C++, and uses lots of parallelization, handled by OpenMP. I need it to be callable from Python, so I created a CPython package, which handles the wrapping of the algorithm.
Now I need some UI, as user interaction is essential for initializing some stuff. My problem is that the UI freezes when I run the algorithm. I start the algorithm in a separate thread, so this shouldn't be a problem (I even proved it by replacing the function call with time.sleep, and it works fine, not causing any freeze). For testing I reduced the UI to two buttons: one for starting the algorithm, and another just to print some random string to console (to check UI interactions).
I also experienced something really weird. If I started moving the mouse, then pressed the button to start the computation, and after that kept moving the mouse continuously, the UI did not freeze, so hovering over the buttons gave them the usual blueish Windows-style tint. But if I stopped moving my mouse for a several seconds over the application window, clicked a button, or swapped to another window, the UI froze again. It's even more strange that the UI stayed active if I rested my mouse outside of the application window.Here's my code (unfortunately I cannot share the algorithm for several reasons, but I hope I manage to get some help even like this):
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from PyQt5.QtCore import QThread, QObject, pyqtSignal
import time
from CustomAlgorithm import Estimator # my custom Python package implemented in C++
class Worker(QObject):
finished = pyqtSignal()
def run(self):
estimator = Estimator()
estimator.calculate()
# if the above two lines are commented, and the next line is uncommented,
# everything's fine
# time.sleep(5)
print("done")
app = QApplication([])
thread = QThread()
window = QWidget()
layout = QVBoxLayout()
# button to start the calculation
btn = QPushButton("start")
layout.addWidget(btn)
btn.clicked.connect(thread.start)
# button to print some text to console
btn2 = QPushButton("other button")
layout.addWidget(btn2)
btn2.clicked.connect(lambda: print("other button clicked"))
window.setLayout(layout)
# handling events
worker = Worker(app)
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
window.show()
app.exec_()
I tried multiple variants of using threads, like threading.Thread, multiprocessing.Process, PyQt5.QtCore.QThread (as seen above), even napari's worker implementation, but the result was the same. I even tried removing omp from the code, just in case it interferes somehow with python threads, but it didn't help.
As for the reason I use python, is that the final goal is to make my implementation available in napari.
Any help would be highly appreciated!
Because of Python's "Global Interpreter Lock", only one thread can run Python code at a time. However, other threads can do I/O at the same time.
If you want to allow other threads to run (just like I/O does) you can surround your code with these macros:
Py_BEGIN_ALLOW_THREADS
// computation goes here
Py_END_ALLOW_THREADS
Other Python threads will be allowed to run while the computation is happening. You can't access anything from Python between these two lines - so get that data in order before Py_BEGIN_ALLOW_THREADS.
Reference

How do I Update Tkinter constantly without freezing everything else?

I need to send updates to tkinter from another thread, which should be handled immediately. Sadly Tkinter freezes whenever I try multithreading.
My Attempt:
I've read through several Tkinter-threaded pages but didn't find anything that works well, since most of them try creating a new thread on button.click(), which doesn't help here.
I tried not calling .mainloop() and instead calling the update functions myself, whenever an update comes in:
#GUI
def update(self, string):
self._interactor.config(text=string)
#interactor is a button
self._tk.update_idletasks()
self._tk.update()
This works fine until I use a loop with sleep() in the master to constantly update the text. GUI and master are frozen during sleep().
So I tried to use a threaded timer as discussed here.
#MASTER
gui=gui_remote(self)
def changetext():
text=self.gettextsomewhere()
self._gui.update(text)
loop=RepeatedTimer(5, changetext)
But this just leads to the following error, thrown by Tkinter:
RuntimeError: main thread is not in main loop
Having a hard time on how to solve this. Is it possible to call a GUI class on main thread and still have proper access to its functions?
How I got to this point:
For my project, I need a button, which represents several Buttons.
Every y (eg. 1.5) seconds, the displayed text should be updated to a new one from the outside.
Also, I want to keep GUI, Controller and Data separated, (using blueprint methods) so that later adjustments on each of them will be easier.
I already got it to work, using TK's .after() function, but I had to use GUI and controlling functions closely together.
My Plan
Have a GUI class, which is updateable from another object via simple public functions. The other object (the master) should be able to create a GUI object and call the GUI's update functions with new data every y seconds.
When the GUI button is clicked, simply call a certain method at master every time:
#GUI example
from tkinter import Tk, Button, Frame, Label
class gui_sample:
def __init__(self, master):
"""This is the very simple GUI"""
self._master=master
self._tk=Tk()
self._interactor= Button(self._tk, text="Apfelsaft", command=self._click)
self._interactor.pack()
self._tk.mainloop()
def update(self, string):
"""Handle interactor update"""
self._interactor.config(text=string)
def _click(self):
self._master.click()
#MASTER
from gui_module import *
class Controller:
def __init__(self):
self._gui=gui_sample(self)
self._run()
def _run(self):
#call this every 5 seconds
new_text=self.gettextfromsomewhere()
self._gui.update(new_text)
def click():
#do something
pass
#this code is just a blueprint it probably does nothing
My Problem:
I don't want the master to use TK functions since I might switch to another UI module later and keep the master's functionality. The master will constantly loop through what's being displayed next and needs to be accessible at the same time. Using loops with sleep() isn't a good idea since they will block both, the master and the GUI. Calling .mainloop() is also problematic since it will block all other programs. The gui should always respond to updates and not ask for them.

wxpython: How to pass XY coordinator and draw it out?

I'm new for python but willing to learn. I have a set of hardware to receive touch coordinators and draw line accordingly to coordinators.
My problem is that the wxpython won't draw line if coordinator changes.
Here is my code : https://github.com/eleghostliu/homework/blob/master/DrawXY_byWxPython/PythonApplication1/PythonApplication1.py
can someone give advise, thanks.
You registered for EVT_PAINT, yet you are not triggering the event as the data changes. The frame has no idea whether data changed or not, unless you specifically inform it.
You can trigger the event simply by calling
frame.Refresh()
You can hook it in several ways. For instance, you could pass frame.Refresh bound method as a parameter to MainProcess so that it can make the function call to refresh the frame. Something like the following:
WARNING: Erroneous code piece
# Start a socket server
def MainProcess(refresh_callback):
while True:
refresh_callback()
***********************************************
frame = DrawPanel()
frame.Show()
start_new_thread(MainProcess, (frame.Refresh,))
Edit:
The above code piece calling UI methods directly is wrong!
Worker thread should not directly manipulate GUI, instead it should inform the GUI thread about a change, and the GUI thread which is the main thread will handle it in its context. There are again several approaches here, the quickest to implement is through wx.CallAfter.
Which you can incorporate as below, instead of directly calling the function:
wx.CallAfter(refresh_callback)
Another way to handle the communication between worker thread and GUI thread is via wx.PostEvent.
class DrawPanel(wx.Frame):
"""Draw a line to a panel."""
def notify(self):
wx.PostEvent(self, wx.PaintEvent())
Then from the secondary thread, you can safely call frame.notify() whenever new data arrives.
For a more elegant solution involving wx.CallAfter, refer to https://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/ where pubsub is used.

About a PyQt example program

I'm currently need GUI library for a project. I'm familiar with python and found PyQt might be a good choice.
I'm reading a tutorial about PyQt, and quite confused about the following example program
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
In this example, we draw text in Russian azbuka.
author: Jan Bodnar
website: zetcode.com
last edited: September 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.text = u'\u041b\u0435\u0432 \u041d\u0438\u043a\u043e\u043b\u0430\
\u0435\u0432\u0438\u0447 \u0422\u043e\u043b\u0441\u0442\u043e\u0439: \n\
\u0410\u043d\u043d\u0430 \u041a\u0430\u0440\u0435\u043d\u0438\u043d\u0430'
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Draw text')
self.show()
def paintEvent(self, event):
qp = QtGui.QPainter()
qp.begin(self)
self.drawText(event, qp)
qp.end()
def drawText(self, event, qp):
qp.setPen(QtGui.QColor(168, 34, 3))
qp.setFont(QtGui.QFont('Decorative', 10))
qp.drawText(event.rect(), QtCore.Qt.AlignCenter, self.text)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Here, in main function, an Example object is created, thus __init__() function, initUI() is called.
My question is where does paintEvent() function is called?? since if we run the program, self.text(some Russian letters) will exactly appear on the widget.
In other words, what does sys.exit(app.exec_()) actually do? why it will call paintEvent() function?
Thanks!
From PyQt docs:
int QApplication.exec_ ()
Enters the main event loop and waits until exit() is called, then
returns the value that was set to exit() (which is 0 if exit() is
called via quit()).
It is necessary to call this function to start event handling. The
main event loop receives events from the window system and dispatches
these to the application widgets.
From another source:
sys.exit(app.exec_())
Finally, we enter the mainloop of the
application. The event handling starts from this point. The mainloop
receives events from the window system and dispatches them to the
application widgets. The mainloop ends, if we call the exit() method
or the main widget is destroyed. The sys.exit() method ensures a clean
exit. The environment will be informed, how the application ended.
The exec_() method has an underscore. It is because the exec is a
Python keyword. And thus, exec_() was used instead.
About painting:
4.2.1. When Painting Occurs
The paintEvent() method is called automatically when
Your widget is shown for the first time.
After a window has been moved to reveal some part (or all) of the
widget.
The window in which the widget lies is restored after being minimized.
The window in which the widget lies is resized.
The user switches from another desktop to the desktop on which the
widget's window lies.
You can generate paint events manually by calling QWidget::update().
QWidget::update() erases the widget before generating the paint event.
You can pass arguments to update(), which can restrict painting only
to areas (rectangles, in particular) that need it. The two equivalent
forms of the method are QWidget::update (int x, int y, int width, int
height) and QWidget::update (QRect rectangle), where x and y give the
upper-left corner of the rectangle, and width and height are obvious.
Because update() places a paint event into the event queue, no
painting occurs until the current method exits and control returns to
the event handler. This is a good thing because other events may be
waiting there to be processed, and events need to be processed in a
timely manner for the GUI to operate smoothly.
You can also invoke painting of the widget by calling QWidget::repaint (int x, int y, int width, int height, bool erase) (or one of several
convenience-method forms), where all the arguments mean the same as in
the case of the update() method, and erase tells repaint whether to
erase the rectangle before painting it. repaint() calls paintEvent()
directly. It does not place a paint event into the event queue, so use
this method with care. If you try to call repaint() repeatedly from a
simple loop to create an animation, for example, the animation will be
drawn, but the rest of your user interface will be unresponsive
because the events corresponding to mouse button clicks, keyboard
presses, and so on will be waiting in the queue. Even if you are not
performing a task as potentially time-consuming as animation, it is
generally better to use update() to help keep your GUI alive.
If you paint something on your widget outside the paintEvent(), you
still need to include the logic and commands necessary to paint that
same thing in paintEvent(). Otherwise, the painting you did would
disappear the next time the widget is updated.
It becomes more clear when one has some experience with low level programming,
for example in Winapi or the X toolkit in C language. PyQt is a (very) high level toolkit.
It comes with a huge built-in functionality. Similar example would require hundreds or maybe thousands of lines of C code. As a consequence, there is a lot of going on
behind the scenes. Somebody already created code that deals with painting on a basic level.
GUI programming is very complex and with modern GUI toolkits the application programmer is shielded from this complexity. It is inevitable that programmers are confused if they do not know all the technical details.
In PyQt, we are essentially dealing with events in two ways. We connect signals to slots or reimplement event handlers (an event handler is a synonym for a slot). The provided example inherited from the QtGui.QWidget which already has some painting code available.
In order to do our custom painting, we have to reimplement the existing paintEvent() event
handler. Depending on the situation, we may or may not call the parent's paintEvent() method.
The sys.exit(app.exec_()) does not call the paintEvent() method. The exec_() method starts an event loop. The event loop catches and dispatches events. Paint events are triggered by users or by the operating system. For example, when we launch the example, the
paint event is triggered twice. (Put a print "Event occurred" line in the event handler to see how many times this method is called.) Resizing windows, loosing or gaining focus, minimizing or maximizing windows, all these cause painting events to be triggered.
app.exec_() starts the qt main loop, which ends when every created widget is destroyed (e.g. by closing its window). The paintEvent function is a method that you can overload from a QWidget subclass like the given Example class, which gets called when QT displays, updates or repaints the Widget.
You can look up these things in the Qt documentation or the PyQt Documentation (which is mostly just a copy of the QT Documentation in a different format, but sometimes contains some valuable information regarding PyQt-specific things).

Categories