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.