Tkinter / pylab conflict? - python

I'm very new to Python but have started writing a few small scripts this week. I'm currently trying to write a simple program to plot some data. I'd like to do the following:
ask the user to choose the data directory using a GUI
for each file in the directory, make a plot
close each plot with a mouse click and advance to the next plot
I've mostly gotten the program to work - I can choose the directory using tkFileDialog.askdirectory, then read in the data, make the plots and advance though them using a mouse click.
My problem is with the TK root window that opens with the tkFileDialog. If I use withdraw() the extra window doesn't open, but only the first plot will appear (a mouse click closes that plot but doesn't show the next one). If I don't use withdraw(), the extra window must be manually closed after the first plot to advance to the second.
I'm wondering if there is a way to choose the directory that will avoid displaying the extra window?
I'm attaching some sample code to show my thought process. This doesn't call the actual data but still reproduces the problem (you'll need to change the .D to some file type that you have in a directory):
import numpy as np
from pylab import *
import glob
import os
import Tkinter, tkFileDialog
##################################################
#define the mouse click event
##################################################
def moveon(event):
close()
##################################################
#ask for the directory
##################################################
root = Tkinter.Tk()
#root.withdraw()
direc = tkFileDialog.askdirectory(parent=root,initialdir="/",title='Please select a directory')
os.chdir(direc)
for files in glob.glob("*.D*"):
##################################################
#Read in the data
##################################################
#assume this reads x and y from each file
x = [1, 2]
y = [3, 4]
##################################################
#loop though the plots
##################################################
fig = figure(1)
plot(x,y)
cid = fig.canvas.mpl_connect('button_press_event',moveon)
show()

Since you don't seem to be using Tkinter after your file dialog, you could do root.destroy()
to close the Tk root window right after you have the user select a file.

Related

Terminal is busy after TKinter window is closed

I have written a python script that draws a GUI containing Graphs plotting various metrics such as CPU temperature, Ram usage etc. for my Rasberry pi v4 . The source code of the script is outlined below:
`
#!/usr/bin/env python3
#Import required modules
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import time
from gpiozero import CPUTemperature
import os
import psutil
import sys
#Create application GUI
Main_Window=tk.Tk()
Main_Window.title("Monitors v1.0")
window_width = 800
window_height = 400
Main_Window.resizable(False,False)
Main_Window.geometry(f'{window_width}x{window_height}')
#Set up the layout of the graphs .There will be 4 graphs plotting:
#CPU Temperature in Celsius,RAM,CPU and Disk usage usage in %
fig,(axs)=plt.subplots(2,2,constrained_layout=True,figsize=(8, 4))
canvas=FigureCanvasTkAgg(fig, Main_Window)
canvas.get_tk_widget().place(x=0,y=0)
fig.suptitle('Performance Metrics')
axs[0,0].set_ylabel('CPU Temperature(C)')
axs[0,0].set_xlabel('Time(Sec)')
axs[0,1].set_ylabel('RAM Usage(%)')
axs[0,1].set_xlabel('Time(Sec)')
axs[1,0].set_ylabel('CPU Usage(%)')
axs[1,0].set_xlabel('Time(%)')
axs[1,1].set_ylabel('Disk Usage(%)')
axs[1,1].set_xlabel('Time(%)')
#Initialize data buffers and plots
temp=[]
RAM=[]
CPU=[]
Disk=[]
axs[0,0].plot(temp)
axs[0,1].plot(RAM)
axs[1,0].plot(CPU)
axs[1,1].plot(Disk)
#Run the script for 50 samples
for _ in range(50):
#Sampling the metrics.
temp.append(CPUTemperature().temperature)
RAM.append(psutil.virtual_memory()[2])
load1, load5, load15 = psutil.getloadavg()
cpu_usage = (load15/os.cpu_count()) * 100
CPU.append(cpu_usage)
Disk.append(psutil.disk_usage('/').percent)
#Update the Plots every 200 msec
time.sleep(0.2)
canvas.draw()
axs[0,0].clear()
axs[0,1].clear()
axs[1,0].clear()
axs[1,1].clear()
axs[0,0].set_ylabel('CPU Temperature(C)')
axs[0,0].set_xlabel('Time(Sec)')
axs[0,1].set_ylabel('RAM Usage(%)')
axs[0,1].set_xlabel('Time(Sec)')
axs[1,0].set_ylabel('CPU Usage(%)')
axs[1,0].set_xlabel('Time(%)')
axs[1,1].set_ylabel('Disk Usage(%)')
axs[1,1].set_xlabel('Time(sec)')
axs[0,0].plot(temp)
axs[0,1].plot(RAM)
axs[1,0].plot(CPU)
axs[1,1].plot(Disk)
Main_Window.mainloop()
sys.exit()
I have also created a bash command so that I can run the script with a simple command via the terminal. The script runs as supposed to be plotting 50 measurements of the metrics described before in real time but there are two issues I am facing. First, despite clicking the close button(the classic X button on the top right corner) the window wont close until the 50 measurements are done. Then if I close the window after the measurements are done, then the terminal looks busy as if the script is still running. Any ideas why these things happen?
I tried adding a sys.exit() command after mainloop() to make sure the script exits after the main window is closed but it did not help.
When you enter your for-loop, your program runs all 50 iteration of that code block until it exits and runs code below. This is toxic for different reasons. Overall, your mainloop is interrupted, events such as mouse or key input won't be processed. This includes clicking X, the window simply does not proceed this message.
However, Tkinter provides tools for such operations. You could use after for it:
Example:
def my_function():
#useful instructions
for i in range(50):
ms = 20*i
root.after(ms, my_function)
After returns immediately and won't block your application, means still responsive to input. But since you still want to sleep in the block of code, you could use something like tksleep.

How to fix Tkinter dead frezzing or crashing because of huge data?

I am trying to make a program to display one single image (.png extension) at once but giving a button to the user to change the picture.
What I have done is:
Reading the Image from my directory with help of Pillow module
Appended it to a list
With a button I increase or decrease the index of the list.
(Note I have to read 600 images approx.)
Here's the code:
import os
from tkinter import *
from PIL import ImageTk,Image
import threading,time
#Define the tkinter instance
x=0
win= Tk()
dir_path= os.path.dirname(os.path.realpath(__file__))
print(dir_path)
l1=[]
#Define the size of the tkinter frame
win.geometry("700x400")
def start():
threading.Thread(target=bg).start()
win.after(5000,threading.Thread(target=fg).start())
#Define the function to start the thread
def bg():
print("bg")
for i in range(1,604):
a=Image.open(f"{dir_path}\\{i}.png")
a=a.resize((500,700), Image.ANTIALIAS)
b=ImageTk.PhotoImage(a)
l1.append(b)
print(b)
print(len(l1))
def fg():
def add():
global x
x+=1
img2=l1[x]
d.configure(image=img2)
d.image = img2
d.update()
global d
d=Label(win,image=l1[x])
d.pack()
Button(win,text="add",command=add).place(x=0,y=0)
label= Label(win)
label.pack(pady=20)
#Create button
b1= Button(win,text= "Start", command=start)
b1.pack(pady=20)
win.mainloop()
But the problem is that the Tkinter gets dead freeze and laggy to an extent that the GUI is not operable.
So my question is,
How to fix Tkinter dead Frezzes and if there is any way to read the images as fast as possible?
The freezes of Tkinter depends on reading speed of the interpreter, because:
continuously reading and Showing the pictures are a huge job for Python.
reading images using opencv or any other image processing module wont help as reading part can become faster, but showing the image in tkinter is done using python only, so rapid changes in Label will cause tkinter to crash.
Solution:
Switch to a different compiler based language for example c++.
this solution is specifically for a image slideshow,
The solution is to use selenium with python. You can specify the image location in driver.get(image_path) and it will open the image for you, and if you want to change the image with a button, then just rename all your images with the numbers and make a button to add +1 to the index.

Generic Issue with Tkinter to create a GUI Application in Python

I have a general problem, which I sketch here as the details will be too involved to post here. I know the problem statement, is a bit fuzzy and I may need to go back and forth with an expert in this site. (unfortunately, it is difficult to put everything up here based on the type of the problem. I will really appreciate any help).
I am trying to create a GUI application using Tkinter. The way I am doing is as follows. I have a background script (say back.py) which has the data loaded, calculations done and graph plotted.
Now the way I want to do is that I have a GUI script which uses Tkinter and calls the back.py ( using import). Now I have the window created, with a button. This is where I am stuck. I want to click the button to trigger the background script and generate the plots ( which the background script generates).
After this I want to close the plot and want my GUI to pop up some buttons to input me some parameters. These parameters will be input to the next part of the back.py code ( I chose the parameters based on the plot). When I again click the button ( with the parameters selected), I want to start running the background code again which will output me a file.
How can I do this?. A general rough idea will be helpful.
Let me put an example as much as I can:( at least the skeleton of the code)
I have a background file say (a.py) and gui file ( say g.py)
a.py
import ...
def progA():
# reading a file
# doing something and generating a plot from the file
# Once the GUI's first part is done generating the plot, I need to close that
# plot (or button) and then click the button 2 to run the next function
def progB(y1, y2,y3):
#run a code... and generate an output file
g.py
from tkinter import *
from tkinter.ttk import *
class GUI ():
def create widgets(self):
#....
def create panel(self):
#create buttons
panel1 = ...
btn1 = Button(panel1, text="yyyyy", command=progA)
btn1.pack()
def create_panel1(self):
#create buttons
panel1 = ...
btn1 = Button(panel1, text="yyyyy", command=progA)
btn1.pack()
def create_panel2(self):
#create buttons
panel2 = ...
btn2 = Button(panel1, text="yyyyy", command=progB)
btn2.pack()
All_Entries = []
window = Tk()
D=GUI(window)
window.mainloop()
import a
runprogram1 = a.progA()
runprogram2 = a.probB(x, y, z)
My question is now, does the above makes sense? So I have a couple of questions:
How will I ensure that when I close the plots (from the output of progA), that the second button will show up?
Where can I input the there values of the parameters in the second button?

How to modify Page Py output

I just moved from Matlab to Python. So I am looking hopefully to re-build my GUI in Matlab Guide with Page Python (only hopeful for better performance for big data)
I designed several Push Buttons and inside the code I try to write the below code just beneath Button1.configure.
I can not take out seop value from this clicked () function although I even define it as global variable. I need seop for whole the program.
self.Button1.configure(text='''Data''')
def clicked():
global seop
from tkinter import filedialog, messagebox
fname = filedialog.askopenfilename (initialdir="C:\Sgty")
import numpy as np
seop = np.loadtxt (fname)
messagebox.showinfo ('Data Import', 'Int')
s1 = self.Button1.configure (command=clicked, text="Import Data")

Repeated dialog window with Tkinter and Matplotlib on Mac OS X

I'm newbie in Tkinter. I try to use next code to open a file using tkFileDialog.askopenfilename and then plot something with Matplotlib:
import matplotlib.pyplot as plt
import Tkinter, tkFileDialog
root = Tkinter.Tk()
root.withdraw()
file_path = tkFileDialog.askopenfilename()
x = range(10)
plt.plot(x)
plt.show()
After running the above script I get dialog window to open my file. After file selection I get repeated dialog window to open the file and a new window at the bottom of my screen. I know that the problem is because of plt.show(). What happens and how to avoid dialog window reopening? Should I set a Matplotlib backend for my task?
My versions:
Tcl/Tk 8.5.9
Matplotlib 1.3.1
Tkinter $Revision: 81008 $
OS X 10.9.4
I have found two related stackoverflow questions:
pyplot-show-reopens-old-tkinter-dialog and
matplotlib-figures-not-working-after-tkinter-file-dialog
but no answers. It seems that root.destroy() is not working for me.
When run with python test.py, the following seems to work for me:
import matplotlib.pyplot as plt
import Tkinter, tkFileDialog
root = Tkinter.Tk()
root.withdraw()
file_path = tkFileDialog.askopenfilename()
root.destroy()
print file_path
x = range(10)
plt.plot(x)
plt.show()
I think it works because the Tk instance for the file dialog is destroyed before matplotlib fires up its own. Interestingly, it also works for me when run from
ipython --pylab=tk
where I would have expected a problem with starting the event loop twice. The canonical solution in this case would be to check if Tk is already running before firing it up (again).
I'm on MacOSX 10.7.5, custom built matplotlib (shouldn't matter).
The only thing I noticed was that after experimenting with this, the touchpad swipe gestures on my Mac no longer work... Looking into this.
Edit
Here is a breakdown of the Tk commands executed by tkFileDialog.askopenfilename():
# breakdown of tkFileDialog.askopenfilename()
import Tkinter as Tk
window = Tk.Tk()
window.withdraw()
w = Tk.Frame(window)
s = w.tk.call('tk_getOpenFile', *w._options({}))
print s
w.destroy()
window.destroy()
When I run this (with python test.py), I get the file open dialog, where I can choose a file. Upon "OK" it prints the file name and exits. This works every time on my system. However, sometimes the 3-finger gestures on my touchpad stop working while running this program! And they don't come back after the program exits!! Not even after I kill the terminal the program was running in!!!
The only way I found to bring them back is to add the following matplotlib code to test.py:
import matplotlib
matplotlib.use('tkagg')
import matplotlib.pyplot as plt
plt.figure() # simplified from plt.plot(range(10))
plt.show()
and then click on the title bar of "Figure 1". This instantly brings back the 3-finger gestures. I suspect this is simply a bug in Tkinter. (I'm on Tcl/Tk 8.5, by the way)
I cannot reproduce the behavior that the file open dialog is constantly relaunched on my system.
Could you please describe what happens on your system if you launch test.py, without any matplotlib commands?
Alternatively, since Tkinter is old and apparently buggy, may I suggest to use Qt instead? Not only does it look much nicer, it is also snappier and I didn't have any problems with bugs.
Edit 2
I have broken down the Tk actions that matplotlib takes when executing the above commands in a non-interactive environment (i.e. with python test.py and not from iPython). These are the essential backend calls:
import matplotlib.backends.backend_tkagg as backend
figManager = backend.new_figure_manager(1)
figManager.show()
backend.show.mainloop()
These are still backend independent. I.e., for a Qt figure, simply use:
import matplotlib.backends.backend_qt4agg as backend
If we break this down further to the backend-specific layer, we have:
import matplotlib.backends.backend_tkagg as backend
import Tkinter as Tk
window = Tk.Tk()
window.withdraw()
# uncomment this to use the same Tk instance for tkFileDialog and matplotlib
#import tkFileDialog
#fname = tkFileDialog.askopenfilename(master=window)
#print fname
# figManager = backend.new_figure_manager(1)
from matplotlib.figure import Figure
figure = Figure()
canvas = backend.FigureCanvasTkAgg(figure, master=window)
figManager = backend.FigureManagerTkAgg(canvas, 1, window)
# figManager.show()
window.deiconify()
# backend.show.mainloop()
Tk.mainloop()
First, run with the tkFileDialog calls commented out and check if the matplotlib figure appears and behaves correctly. Then uncomment the tkFileDialog calls and see if you finally get the expected behavior.
If not, one has to continue breaking down FigureCanvasTkAgg and FigureManagerTkAgg to understand what is going on...
Edit 3
OK, since the problem persisted, let's break down matplotlib's Tk calls even further. The following code completely isolates all of matplotlib's Tk actions that I consider essential (so it is no longer necessary to import anything from matplotlib!). Note that I left out generating the toolbar and assigning lots of callbacks and keypress events. If the code below works now, then the problem lies with these. If it doesn't work, we can conclude that it is purely a Tk problem, and most likely a bug that should be reported. Here is the code:
import Tkinter as Tk
window = Tk.Tk()
window.withdraw()
# uncomment this to use the same Tk instance for tkFileDialog and matplotlib
#w = Tk.Frame(window)
#fname = w.tk.call('tk_getOpenFile', *w._options({}))
#print fname
#w.destroy()
# canvas = backend.FigureCanvasTkAgg(figure, master=window)
_tkcanvas = Tk.Canvas(master=window, width=640, height=480, borderwidth=4)
_tkphoto = Tk.PhotoImage(master=_tkcanvas, width=640, height=480)
_tkcanvas.create_image(320, 240, image=_tkphoto)
_tkcanvas.focus_set()
# figManager = backend.FigureManagerTkAgg(canvas, 1, window)
window.wm_title("Figure 1")
window.minsize(480, 360)
_tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
# figManager.show()
window.deiconify()
# backend.show.mainloop()
Tk.mainloop()
Please play around with commenting out some lines and see if you can get it working correctly. If not, I would conclude that this is a bug in Tkinter, which should be reported.

Categories

Resources