Nov-11-2020, 12:09 AM
Hello,
I am new to this forum and apologize if I'm posting in the wrong area or this type of question is not meant for this forum. I'm looking for some guidance regarding a fundamental issue I've localized to threaded functionality.
I'm working on a higher performance plotter for matplotlib which I think I've made substantial progress on. I want to spawn the plot off my GUI so to prevent it from blocking I figured I need to make it a daemon thread so users could still interact with the plot and the GUI. I generally use this approach when I need to launch a task and don't want it to block GUI functionality. I have to plotting working unthreaded, but when I put it in a thread class I can't inteeract (drag the plot window around for example). I feel like I'm missing something fundamental here so thats why I've been looking for a way to ask experts. I'm an EE not a software engineer so my education in this is less than optimal.
I've tried to create two Python modules that show and and isolate the issue. I've attached a zip file with two python files. Sanbox1.py works and seems to behave like I'd expect. Sandbox2.py is the same code basically added to the "Thread" class. My goal was to spawn this plotting functionality off on a separate thread. I can't figure out why Sandbox2.py works differently land doesn't let me drag the window around or interact with it. If I try it just crashes.
I found this documentation that I have been following and studying to try to understand this.
Example From Documentation
I couldn't figure out how to attach a zip file so the code is below:
Sandbox1.py - working well
Windows 10
Python 2.7.17
matplotlib 2.2.5
Any advice is greatly appreciated! Thank you for your time!
I am new to this forum and apologize if I'm posting in the wrong area or this type of question is not meant for this forum. I'm looking for some guidance regarding a fundamental issue I've localized to threaded functionality.
I'm working on a higher performance plotter for matplotlib which I think I've made substantial progress on. I want to spawn the plot off my GUI so to prevent it from blocking I figured I need to make it a daemon thread so users could still interact with the plot and the GUI. I generally use this approach when I need to launch a task and don't want it to block GUI functionality. I have to plotting working unthreaded, but when I put it in a thread class I can't inteeract (drag the plot window around for example). I feel like I'm missing something fundamental here so thats why I've been looking for a way to ask experts. I'm an EE not a software engineer so my education in this is less than optimal.
I've tried to create two Python modules that show and and isolate the issue. I've attached a zip file with two python files. Sanbox1.py works and seems to behave like I'd expect. Sandbox2.py is the same code basically added to the "Thread" class. My goal was to spawn this plotting functionality off on a separate thread. I can't figure out why Sandbox2.py works differently land doesn't let me drag the window around or interact with it. If I try it just crashes.
I found this documentation that I have been following and studying to try to understand this.
Example From Documentation
I couldn't figure out how to attach a zip file so the code is below:
Sandbox1.py - working well
import matplotlib.pyplot as plt import numpy as np class BlitManager: def __init__(self, canvas, animated_artists=()): """ Parameters ---------- canvas : FigureCanvasAgg The canvas to work with, this only works for sub-classes of the Agg canvas which have the `~FigureCanvasAgg.copy_from_bbox` and `~FigureCanvasAgg.restore_region` methods. animated_artists : Iterable[Artist] List of the artists to manage """ self.canvas = canvas self._bg = None self._artists = [] for a in animated_artists: self.add_artist(a) # grab the background on every draw self.cid = canvas.mpl_connect("draw_event", self.on_draw) def on_draw(self, event): """Callback to register with 'draw_event'.""" cv = self.canvas if event is not None: if event.canvas != cv: raise RuntimeError self._bg = cv.copy_from_bbox(cv.figure.bbox) self._draw_animated() def add_artist(self, art): """ Add an artist to be managed. Parameters ---------- art : Artist The artist to be added. Will be set to 'animated' (just to be safe). *art* must be in the figure associated with the canvas this class is managing. """ if art.figure != self.canvas.figure: raise RuntimeError art.set_animated(True) self._artists.append(art) def _draw_animated(self): """Draw all of the animated artists.""" fig = self.canvas.figure for a in self._artists: fig.draw_artist(a) def update(self): """Update the screen with animated artists.""" cv = self.canvas fig = cv.figure # paranoia in case we missed the draw event, if self._bg is None: self.on_draw(None) else: # restore the background cv.restore_region(self._bg) # draw all of the animated artists self._draw_animated() # update the GUI state cv.blit(fig.bbox) # let the GUI event loop process anything it has to do cv.flush_events() class Top(object): def __init__(self): self.x = np.linspace(0, 2 * np.pi, 100) # make a new figure self.fig, self.ax = plt.subplots() # add a line (self.ln,) = self.ax.plot(self.x, np.sin(self.x), animated=True) # add a frame number self.fr_number = self.ax.annotate( "0", (0, 1), xycoords="axes fraction", xytext=(10, -10), textcoords="offset points", ha="left", va="top", animated=True, ) self.bm = BlitManager(self.fig.canvas, [self.ln, self.fr_number]) # make sure our window is on the screen and drawn plt.show(block=False) plt.pause(.1) def loop(self): for j in range(1000): # update the artists self.ln.set_ydata(np.sin(self.x + (j / 1000.) * np.pi)) self.fr_number.set_text("frame: {j}".format(j=j)) # tell the blitting manager to do it's thing self.bm.update() T = Top() T.loop()Sandbox2.py - freezes upon interaction
import matplotlib.pyplot as plt import numpy as np import threading class BlitManager: def __init__(self, canvas, animated_artists=()): """ Parameters ---------- canvas : FigureCanvasAgg The canvas to work with, this only works for sub-classes of the Agg canvas which have the `~FigureCanvasAgg.copy_from_bbox` and `~FigureCanvasAgg.restore_region` methods. animated_artists : Iterable[Artist] List of the artists to manage """ self.canvas = canvas self._bg = None self._artists = [] for a in animated_artists: self.add_artist(a) # grab the background on every draw self.cid = canvas.mpl_connect("draw_event", self.on_draw) def on_draw(self, event): """Callback to register with 'draw_event'.""" cv = self.canvas if event is not None: if event.canvas != cv: raise RuntimeError self._bg = cv.copy_from_bbox(cv.figure.bbox) self._draw_animated() def add_artist(self, art): """ Add an artist to be managed. Parameters ---------- art : Artist The artist to be added. Will be set to 'animated' (just to be safe). *art* must be in the figure associated with the canvas this class is managing. """ if art.figure != self.canvas.figure: raise RuntimeError art.set_animated(True) self._artists.append(art) def _draw_animated(self): """Draw all of the animated artists.""" fig = self.canvas.figure for a in self._artists: fig.draw_artist(a) def update(self): """Update the screen with animated artists.""" cv = self.canvas fig = cv.figure # paranoia in case we missed the draw event, if self._bg is None: self.on_draw(None) else: # restore the background cv.restore_region(self._bg) # draw all of the animated artists self._draw_animated() # update the GUI state cv.blit(fig.bbox) # let the GUI event loop process anything it has to do cv.flush_events() class Top(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.x = np.linspace(0, 2 * np.pi, 100) # thread loop flag self.thread_event = threading.Event() self.thread_event.set() # set thread by default # make a new figure self.fig, self.ax = plt.subplots() # add a line (self.ln,) = self.ax.plot(self.x, np.sin(self.x), animated=True) # add a frame number self.fr_number = self.ax.annotate( "0", (0, 1), xycoords="axes fraction", xytext=(10, -10), textcoords="offset points", ha="left", va="top", animated=True, ) self.bm = BlitManager(self.fig.canvas, [self.ln, self.fr_number]) # make sure our window is on the screen and drawn plt.show(block=False) plt.pause(.1) #def loop(self): def run(self): #for j in range(1000): j = 1 while self.thread_event.is_set(): # update the artists self.ln.set_ydata(np.sin(self.x + (j / 1000.) * np.pi)) self.fr_number.set_text("frame: {j}".format(j=j)) # tell the blitting manager to do it's thing self.bm.update() j = j + 1 T = Top() # T.loop() T.start() T.isDaemon()My versions are:
Windows 10
Python 2.7.17
matplotlib 2.2.5
Any advice is greatly appreciated! Thank you for your time!