Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Plotting with matplotlib in threads

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

like image 888
Fabian Avatar asked Oct 29 '13 15:10

Fabian


People also ask

Is matplotlib thread safe?

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.

Can you use matplotlib in idle?

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.

What is matplotlib use (' AGG ')?

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.


2 Answers

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."
like image 72
treddy Avatar answered Oct 30 '22 02:10

treddy


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()
like image 33
Vrekrer Avatar answered Oct 30 '22 02:10

Vrekrer