I know there are quite some questions on matplotlib and threading, also that pyplot is not threadsave. I couldn't find anything on this particular problem however. What I want to do is: plot a figure and update it every second. For this I wanted to create a thread, but so far I couldn't even get a real plot from the thread. Also, I'm stuck with qt4, so it may be other backends behave different.
Here is a very simple example: A plot is created in plot_a_graph()
. This works fine when called from the main program but delays the further execution of the main code. When called from a thread however, no graph is displayed.
import matplotlib
matplotlib.use("qt4agg")
import matplotlib.pyplot as plt
import threading
import time
def plot_a_graph():
f,a = plt.subplots(1)
line = plt.plot(range(10))
plt.show()
print "plotted graph"
time.sleep(4)
testthread = threading.Thread(target=plot_a_graph)
plot_a_graph() # this works fine, displays the graph and waits
print "that took some time"
testthread.start() # Thread starts, window is opened but no graph appears
print "already there"
Thx for you Help
Matplotlib is not thread-safe: in fact, there are known race conditions that affect certain artists. Hence, if you work with threads, it is your responsibility to set up the proper locks to serialize access to Matplotlib artists. You may be able to work on separate figures from separate threads.
Download the “Install Matplotlib (for PC). bat” file from my web site, and double-click it to run it. Test your installation. Start IDLE (Python 3.4 GUI – 32 bit), type “import matplotlib”, and confirm that this command completes without an error.
The last, Agg, is a non-interactive backend that can only write to files. It is used on Linux, if Matplotlib cannot connect to either an X display or a Wayland display.
My suggestion here is to use the python multiprocessing module instead of the threading module. I've been able to perform only slight modifications to your sample code and successfully offload the matplotlib plotting to a child process while the control flow in the main process continues (see code below).
I do suggest reading the multiprocessing documentation, or any of the plethora of blog posts on the subject, if you want the child process(es) to communicate back & forth with the parent in the context of your larger code control flow (which isn't fully described in your question). Note that multiprocessing has the added advantage of circumventing the python global interpreter lock & allowing you to exploit multi-core computer architectures.
#a slight modification of your code using multiprocessing
import matplotlib
matplotlib.use("qt4agg")
import matplotlib.pyplot as plt
#import threading
#let's try using multiprocessing instead of threading module:
import multiprocessing
import time
#we'll keep the same plotting function, except for one change--I'm going to use the multiprocessing module to report the plotting of the graph from the child process (on another core):
def plot_a_graph():
f,a = plt.subplots(1)
line = plt.plot(range(10))
print multiprocessing.current_process().name,"starting plot show process" #print statement preceded by true process name
plt.show() #I think the code in the child will stop here until the graph is closed
print multiprocessing.current_process().name,"plotted graph" #print statement preceded by true process name
time.sleep(4)
#use the multiprocessing module to perform the plotting activity in another process (i.e., on another core):
job_for_another_core = multiprocessing.Process(target=plot_a_graph,args=())
job_for_another_core.start()
#the follow print statement will also be modified to demonstrate that it comes from the parent process, and should happen without substantial delay as another process performs the plotting operation:
print multiprocessing.current_process().name, "The main process is continuing while another process deals with plotting."
Use a Qt signal to call your plotting function in the main thread
import matplotlib
matplotlib.use("qt4agg")
import matplotlib.pyplot as plt
import threading
import time
from PyQt4 import QtCore
class Call_in_QT_main_loop(QtCore.QObject):
signal = QtCore.pyqtSignal()
def __init__(self, func):
super().__init__()
self.func = func
self.args = list()
self.kwargs = dict()
self.signal.connect(self._target)
def _target(self):
self.func(*self.args, **self.kwargs)
def __call__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
self.signal.emit()
@Call_in_QT_main_loop
def plot_a_graph():
f,a = plt.subplots(1)
line = plt.plot(range(10))
plt.show()
print("plotted graph")
print(threading.current_thread()) # print the thread that runs this code
def worker():
plot_a_graph()
print(threading.current_thread()) # print the thread that runs this code
time.sleep(4)
testthread = threading.Thread(target=worker)
testthread.start()
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