0

I have a fairly generic problem where I have a tkinter gui. I'm spawning sub-windows as sub-classes, and those sub-windows launch computationally heavy processes with unknown time-delays using the multiprocess package. This computationally heavy process occasionally pushes a result into a queue, which is monitored by a graphing loop using matplotlib in the main process of the sub-window class. Using multiple different approaches, which work in other contexts, I've been unable to get this graph to update live with a pretty basic python 3.11 install.

I'm sure there are multiple fundamental things with the packages I'm using which I do not understand. I'm fundamentally a hardware guy, and I hack and slash my way to self-automation (or self-replication) when necessary. I'm working very hard to move away from Matlab and Igor so I can compile things for free w/o spending forever creating my own classes. This particular lunatic greatly appreciates any help the broader community could throw him now that he's wandered in from the wilderness. I am open to broad answers. If the most correct is 'learn QT5', I will. I'm at a dead end.

The first approach uses solution from: Python realtime plotting

This approach works just fine as-is in my python install when I run it directly from console. It doesn't work when launched within spyder for some known issues. It breaks down when i run it directly from console within my class:

# -*- coding: utf-8 -*-
"""
Spyder Editor

This is a temporary script file.

Made by Matthew Earl Wallace the Reed
"""
#import os
import time
import numpy as np
#import cv2
#import scipy as sp
import matplotlib.pyplot as plt
#import matplotlib as mpl
try:
    from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg
except ImportError:
    from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk as NavigationToolbar2TkAgg
#from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#import scipy.ndimage as nd
#from scipy.optimize import curve_fit, differential_evolution, least_squares
#import warnings
import multiprocess

#from IPython import get_ipython
#get_ipython().run_line_magic('matplotlib','qt')
#from scipy.interpolate import interp1d
#import dill
import tkinter as tk
#from tkinter import filedialog
#import glob
#import os.path
#from scipy.stats import linregress
#from scipy.stats import norm as normdist
#from copy import deepcopy
#from scipy.signal import find_peaks, peak_widths
#import matplotlib.colors as colors
#from tkinter import messagebox
#from datetime import date
#import threading as td
#import pyautogui as pg
#import multiprocessing as mp
#import time as time
from functools import partial



class trueautoLOL(tk.Toplevel):
    def __init__(self,parent):
        super().__init__(parent)
        lengthtarget=float(tk.simpledialog.askstring('length','feed me length'))
        tolerance=float(tk.simpledialog.askstring('Tolerance','Mas o menos'))
        uniformitytarget=float(tk.simpledialog.askstring('Uniformity','What\'s good brother?'))
        
        self.geometry('300x200')
        self.title('I\'m a horrible window love me anyway')
        q=multiprocess.Queue()
        
        def genplot(parent):
            global line,ax,canvas, fig, x,y, li
            fig2 = plt.figure()
            ax2 = fig2.add_subplot(111)

            # some X and Y data
            x = [0]
            y = [0]

            li, = ax2.plot(x, y,'o')

            # draw and show it
            fig2.canvas.draw()
            plt.show(block=False)
        genplot(self)
        def optloop(q):
            print("Istarted")
            uniformity=np.random.rand(1)*100
            length=np.random.rand(1)*100
            error=200
            counter=0
            while uniformity>uniformitytarget or length>lengthtarget+tolerance or length<lengthtarget-tolerance:
                time.sleep(1)
                theset=np.random.rand(1)*20
                print(theset)
                q.put(theset)
                error=np.random.rand(1)*100
                uniformity=np.random.rand(1)*100
                length=np.random.rand(1)*100
                counter=counter+1
                print(q)
            q.put('Q')
            
        def updateplot(q):
            try:
                result=q.get(False)
                print(result)
                
                if result != 'Q':
                    print(result)
                    x=[0,1,2]
                    y=[0,result,2]

                    # set the new data
                    li.set_xdata(x)
                    li.set_ydata(y)

                    ax.relim() 
                    ax.autoscale_view(True,True,True) 

                    fig.canvas.draw()

                    plt.pause(1)
                   
                    updateplot(q)
  
                else:
                    print('done')
            except:
                print("empty")
                self.after(500,updateplot,q)
                
        theprocess=multiprocess.Process(target=optloop,args=[q])
        theprocess.start()
        print(theprocess.is_alive())
        updateplot(q)
def autoLOL():
    window=tk.Tk()
    window.title("LOOOL")
    window.geometry('900x250')
    updateLOL=tk.Button(window, text="True AutoLOL", command=partial(trueautoLOL,window))
    updateLOL.grid(column=3,row=3)
    window.mainloop()
if __name__=='__main__':
    autoLOL()
                             
    
            
        
        


The second approach attempts to use the tkinter canvas directly (and was my chronological first approach, into which I hacked-and-slashed the approach above).

The notable thing about this approach is that it WORKS, but only when launched from within spyder on a bog-standard anaconda 3.9 install with the extra packages installed w/ pip, but not when launched from spyder with a manual installation on python 3.11.

I'd very much like to cite the source of the overall architecture, but I copied it over sufficiently long ago I can no longer find it...

# -*- coding: utf-8 -*-
"""
Spyder Editor

This is a temporary script file.

Made by Matthew Earl Wallace the Reed
"""
import os
import time
import numpy as np
import cv2
import scipy as sp
import matplotlib.pyplot as plt
import matplotlib as mpl
try:
    from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg
except ImportError:
    from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk as NavigationToolbar2TkAgg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import scipy.ndimage as nd
from scipy.optimize import curve_fit, differential_evolution, least_squares
import warnings
import multiprocess

from IPython import get_ipython
#get_ipython().run_line_magic('matplotlib','qt')
from scipy.interpolate import interp1d
import dill
import tkinter as tk
from tkinter import filedialog
import glob
import os.path
from scipy.stats import linregress
from scipy.stats import norm as normdist
from copy import deepcopy
from scipy.signal import find_peaks, peak_widths
import matplotlib.colors as colors
from tkinter import messagebox
from datetime import date
import threading as td
import pyautogui as pg
import multiprocessing as mp
import time as time
from functools import partial



class trueautoLOL(tk.Toplevel):
    def __init__(self,parent):
        super().__init__(parent)
        lengthtarget=float(tk.simpledialog.askstring('length','feed me length'))
        tolerance=float(tk.simpledialog.askstring('Tolerance','Mas o menos'))
        uniformitytarget=float(tk.simpledialog.askstring('Uniformity','What\'s good brother?'))
        
        self.geometry('300x200')
        self.title('I\'m a horrible window love me anyway')
        q=multiprocess.Queue()
        
        def genplot(parent):
            global line,ax,canvas, fig
            fig=mpl.figure.Figure()
            ax=fig.add_subplot(1,1,1)
            canvas = FigureCanvasTkAgg(fig, master=parent)
            canvas.draw()
            canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
            canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
            plt.show(block=False)
            line, = ax.plot([1,2,3], [1,2,10])
        genplot(self)
        def optloop(q):
            print("Istarted")
            uniformity=np.random.rand(1)*100
            length=np.random.rand(1)*100
            error=200
            counter=0
            while uniformity>uniformitytarget or length>lengthtarget+tolerance or length<lengthtarget-tolerance:
                time.sleep(1)
                theset=np.random.rand(1)*20
                print(theset)
                q.put(theset)
                error=np.random.rand(1)*100
                uniformity=np.random.rand(1)*100
                length=np.random.rand(1)*100
                counter=counter+1
                print(q)
            q.put('Q')
            
        def updateplot(q):
            try:
                result=q.get(False)
                print(result)
                
                if result != 'Q':
                    print(result)
                    line.set_ydata(1,result,10)
                    ax.draw_artist(line)
                    ax.relim()
                    ax.autoscale_view(True,True,True)
                    fig.canvas.draw()
                    canvas.draw()
                    plt.show(block=False)
                   
                    self.after(500,updateplot,q)
  
                else:
                    print('done')
            except:
                print("empty")
                self.after(500,updateplot,q)
                
        theprocess=multiprocess.Process(target=optloop,args=[q])
        theprocess.start()
        print(theprocess.is_alive())
        updateplot(q)
def autoLOL():
    window=tk.Tk()
    window.title("LOOOL")
    window.geometry('900x250')
    updateLOL=tk.Button(window, text="True AutoLOL", command=partial(trueautoLOL,window))
    updateLOL.grid(column=3,row=3)
    window.mainloop()
if __name__=='__main__':
    autoLOL()

I've tried multiple solutions to live-updates of a matplotlib plot using a tkinter gui where the plot needs to periodically update in an infinite loop while fed data asynchronously by another process. Those solutions each individually work in very specific contexts, but fail in the context of my class-based multiprocessing architecture. I am open to anything that will work when 1. Function calls need to be performed on functions defined outside the class 2. some global variables are defined outside the class for said function calls and 3. the plotting update can happen periodically.

I've been working on this on-and-off for a month. My sole purpose is to monitor and plot data which is periodically output. If this can be done through file I/O or whatever, I'll do anything, simple or obtuse.

Matt Reed
  • 1
  • 1

1 Answers1

0

This was mostly a problem with multiprocessing & not fully understanding the matplotlib backend. The source for the canvas-based multiprocessing approach wasn't protecting things with

if __name__=='__main__':

so I wasn't either. When the multiprocess.Process was pickling (or dill'ing, as multiprocess module does) it was trying to re-run the functions initializing the window, re-start plots etc, which was frustrating the matplotlib backend.

The version version of the code I got to work is below:

    class trueautoLOL(tk.Toplevel):
        def __init__(self,parent):
            super().__init__(parent)
            lengthtarget=float(tk.simpledialog.askstring('length','feed me length'))
            tolerance=float(tk.simpledialog.askstring('Tolerance','Mas o menos'))
      
  uniformitytarget=float(tk.simpledialog.askstring('Uniformity','What\'s good brother?'))
    
            self.geometry('300x200')
            self.title('I\'m a horrible window love me anyway')
            q=multiprocess.Queue()
    
            fig = plt.figure()
            ax = fig.add_subplot(111)

                # some X and Y data
            x = [0,1,2,3,4]
            y = [0,1,2,3,4]

            li, = ax.plot(x, y,'o')

            # draw and show it
            if __name__=='__main__':
                fig.canvas.draw()
                plt.show(block=False)

            # loop to update the dat

            q=multiprocess.Queue()
            def datagen(qq):
                qual=100
                while qual<100.5:
                    data = [np.random.rand(1)*4,np.random.rand(1)*4,np.random.rand(1)*4,np.random.rand(1)*4,np.random.rand(1)*4 ]
                    qq.put(data)
                    qual = np.random.rand(1)*102
                    plt.pause(2)
                qq.put('Q')
            def plotter(qq):
                    try:
                
                        y=qq.get()
                        if y!='Q':

                            # set the new data
                            li.set_xdata(x)
                            li.set_ydata(y)

                            ax.relim() 
                            ax.autoscale_view(True,True,True) 

                            fig.canvas.draw()

                            plt.pause(1)
                            plotter(qq)
                        else:
                            print('Done')
                            plt.pause(1)
                    except:
                        print("empty")
                        plt.pause(1)
                        plotter(qq)
            if __name__=='__main__':
                theprocess=multiprocess.Process(target=datagen,args=[q])
                theprocess.start()
                plotter(q)
    def autoLOL():
        window=tk.Tk()
        window.title("LOOOL")
        window.geometry('900x250')
        updateLOL=tk.Button(window, text="True AutoLOL",command=partial(trueautoLOL,window))
        updateLOL.grid(column=3,row=3)
        window.mainloop()
    if __name__=='__main__':
        autoLOL()
Matt Reed
  • 1
  • 1