How to get an array of tkinter custom GUI objects - python

I am trying to get an array of a custom tkinter object (it is a GUI window that shows a video stream).
I am running into errors with the code I tried to write.
I made a function to make the custom object and tried to make an array of those objects.
I am getting the following error:
File "array_videoFeeds.py", line 60, in
app = FeedCams(root) File "array_videoFeeds.py", line 8, in init
self.initialize() File "array_videoFeeds.py", line 54, in initialize
self.b = self.videowindow() File "array_videoFeeds.py", line 11, in videowindow
self.videowindow = window NameError: name 'window' is not defined
Here is my python code:
import tkinter as Tk
import numpy as np
class FeedCams(Tk.Frame):
def __init__(self,parent):
Tk.Frame.__init__(self, parent)
self.parent = parent
self.initialize()
def videowindow(self):
self.videowindow = window
self.cap = cap
self.width = self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.interval = 20 # Interval in ms to get the latest frame
# Create canvas for image
self.canvas = tk.Canvas(self.window, width=self.width, height=self.height)
# self.canvas.grid(row=0, column=0)
# Update image on canvas
self.update_image()
dynamic_windows.append(videowindow)
def update_image(self):
# Get the latest frame and convert image format
self.image = cv2.cvtColor(self.cap.read()[1], cv2.COLOR_BGR2RGB) # to RGB
self.image = Image.fromarray(self.image) # to PIL format
self.image = ImageTk.PhotoImage(self.image) # to ImageTk format
# Update image
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.image)
# Repeat every 'interval' ms
self.videowindow.after(self.interval, self.update_image)
def initialize(self):
'''
Draw the GUI
'''
self.parent.title("RUN ON START TEST")
self.parent.grid_rowconfigure(1,weight=1)
self.parent.grid_columnconfigure(1,weight=1)
self.frame = Tk.Frame(self.parent)
self.frame.pack(fill=Tk.X, padx=5, pady=5)
# Create a array of videos
self.a = np.zeros((3,3))
for i in range(0,self.a.shape[0]):
for j in range(0,self.a.shape[1]):
self.b = self.videowindow()
self.b.grid(row=i, column= j)
# Start the main program here
if __name__ == "__main__":
root=Tk.Tk()
app = FeedCams(root)
root.mainloop()
How can I successfully make this work?
UPDATE:
I changed the line of code based on one of the replies to self.a = np.zeros((3,3)).
There error has been updated in the question.

Related

How can I get elements in Tkinter windows to fill the frame?

I am new to Python GUI development with Tkinter.
I am trying to make the app window a certain size and I want the webcam view in the window to fill the size of the main window.
When I set the window size, the webcam does not fill the entire window.
How can I make the elements fill the entire window when I customize the size of the window?
Here is my code:
import tkinter as tk
from PIL import Image, ImageTk
import cv2
class MainWindow():
def __init__(self, window, cap):
self.window = window
self.cap = cap
self.width = self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.interval = 20 # Interval in ms to get the latest frame
# Create canvas for image
self.canvas = tk.Canvas(self.window, width=self.width, height=self.height)
self.canvas.grid(row=0, column=0)
self.canvas.pack(fill="both", expand=True)
# Update image on canvas
self.update_image()
def update_image(self):
# Get the latest frame and convert image format
self.image = cv2.cvtColor(self.cap.read()[1], cv2.COLOR_BGR2RGB) # to RGB
self.image = Image.fromarray(self.image) # to PIL format
self.image = ImageTk.PhotoImage(self.image) # to ImageTk format
# Update image
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.image)
# Repeat every 'interval' ms
self.window.after(self.interval, self.update_image)
if __name__ == "__main__":
root = tk.Tk()
root.geometry('600x800')
# root.resizable(width=0, height=0)
MainWindow(root, cv2.VideoCapture(0))
root.mainloop()
Thanks.
All you have to do is resize the image to fit the canvas size. You can either use cv2.resize() or Image.resize() to resize the image.
To get the current canvas size use canvas.winfo_height() and canvas.wifo_width(). Besides that, you should also consider updating the existing image using canvas.itemconfig(tag_id), instead of creating a new one each time.
sample code (I'll be using a label instead of canvas to display the image):
import tkinter as tk
from PIL import Image, ImageTk
import cv2
class MainWindow():
def __init__(self, window, cap):
self.window = window
self.cap = cap
self.interval = 20 # Interval in ms to get the latest frame
# Create canvas for image
self.vid_lbl = tk.Label(self.window)
self.vid_lbl.pack(fill="both", expand=True)
# Update image on canvas
self.update_image()
def update_image(self):
# Get the latest frame and convert image format
width, height = self.vid_lbl.winfo_width(), self.vid_lbl.winfo_height()
self.image = cv2.cvtColor(self.cap.read()[1], cv2.COLOR_BGR2RGB) # to RGB
self.image = cv2.resize(self.image, (width, height), cv2.INTER_AREA)
self.image = Image.fromarray(self.image) # to PIL format
#self.image = self.image.resize((width, height))
self.image = ImageTk.PhotoImage(self.image) # to ImageTk format
self.vid_lbl.config(image=self.image)
# Repeat every 'interval' ms
self.window.after(self.interval, self.update_image)
if __name__ == "__main__":
root = tk.Tk()
root.geometry('600x800')
# root.resizable(width=0, height=0)
MainWindow(root, cv2.VideoCapture(0))
root.mainloop()

Resizing Canvas on Tkinter first run issue

I want to make a program that begins as a small window, then when given a path to an image, it maximises the screen and places the image in the centre.
If you run the code below you will see that the window maximises, the image is loaded into memory, the code runs with no errors and self.open_image calls self.draw_image(self.pimg) which runs without error, however the image is not present on the canvas.
If I click the button "Fix" and call self.fix it calls self.draw_image(self.pimg) which runs without error and correctly draws the image.
How can you call the same function twice with the same arguments and get different results. What is different.
I get the feeling this is happening because something has taken place in the main loop that hasn't taken place at the end of self.__init__, so that when i call self.draw_image the second time self.cv.create_image is able to interact with something in the resizable canvas.
In this example I am happy to assume the program will always begin as a small window and become a maximised window untill it is closed, never being resized again, however in my real program I would like to make it more dynamic where the window handles resizing sensibly, this is just a minimum reproducible example. It is for this reason that I would like to use the ResizingCanvas class (or one like it) even though I feel that it is likely the cause of the issue I am experiencing.
I have tried using breakpoints and stepping through the code watching the variables get created but I cant see the difference between the self.cv the first time around and self.cv after I click the button.
I read about a similar issue here on this question and he suggests binding "<Configure>" To the canvas and passing the coords from the event to the canvas. However this has already been implemented in ResizingCanvas
from tkinter import *
from PIL import Image, ImageTk
class ResizingCanvas(Canvas):
# https://stackoverflow.com/a/22837522/992644
def __init__(self,parent,**kwargs):
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
def on_resize(self,event):
""" determine the ratio of old width/height to new width/height"""
wscale = float(event.width)/self.width
hscale = float(event.height)/self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
class main():
def __init__(self, name = None):
self.root = Tk()
self.name = name # Filename
myframe = Frame(self.root)
myframe.pack(fill=BOTH, expand=YES)
self.cv = ResizingCanvas(myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
self.cv.pack(fill=BOTH, expand=YES)
self.b = Button(self.cv, text = 'Fix', command = self.fix).grid(row=1,column=1)
self.open_img()
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
self.cv.create_image(self.root.winfo_screenwidth()/2,
self.root.winfo_screenheight()/2, image=self.img, tags=('all'))
def open_img(self, event=''):
self.pimg = Image.open(self.name)
self.root.state("zoomed")
self.draw_image(self.pimg)
def fix(self, event=''):
self.draw_image(self.pimg)
def run(self):
self.root.mainloop()
if __name__ == "__main__":
path = 'example.png'
app = main(path)
app.run()
What should happen in the video:
I click run and the image is displayed immediately, without having to click the fix button.
What does happen in the video:
I click run and the image is not displayed until I click the fix button, afterwhich it works.
Changing
self.root.state("zoomed") to self.root.state("normal")
in your code (I am working on Python3) I can only get:
[
the image above, played a little bit starting from How to get tkinter canvas to dynamically resize to window width?
and now the code seems to work with me:
from time import sleep
from tkinter import *
from PIL import Image, ImageTk
class ResizingCanvas(Canvas):
# https://stackoverflow.com/a/22837522/992644
def __init__(self,parent, **kwargs):
# Canvas.__init__(self,parent,**kwargs)
print(kwargs)
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
# self.height = self.winfo_reqheight()
# self.width = self.winfo_reqwidth()
self.height = self.winfo_height()
self.width = self.winfo_width()
# self.height = height
# self.width = width
# self.__dict__.update(kwargs)
def on_resize(self,event):
""" determine the ratio of old width/height to new width/height"""
wscale = (event.width)//self.width
hscale = (event.height)//self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
class main():
def __init__(self, name = None):
self.pippo = Tk()
self.name = name # Filename
self.myframe = Frame(self.pippo)
self.myframe.pack(side = BOTTOM, expand=YES)
# myframe.pack(fill=BOTH, expand='TRUE')
self.cv = ResizingCanvas(self.myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
self.cv.pack(fill=BOTH, expand=YES)
# sleep(2)
self.b = Button(self.myframe, text = 'Fix', command = self.fix)#.grid(row=1,column=1)
self.b.pack(side=TOP)
self.open_img()
# self.pippo.mainloop() ## use it if you eliminate def run
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
# self.cv.create_image(self.pippo.winfo_screenwidth()/2,
# self.pippo.winfo_screenheight()/2, image=self.img, tags=('all'))
self.cv.create_image(self.pippo.winfo_width()/2,
self.pippo.winfo_reqheight()/2, image=self.img, tags=('all'))
def open_img(self, event=''):
self.pimg = Image.open(self.name)
self.pippo.state("normal")
self.draw_image(self.pimg)
def fix(self, event=''):
self.draw_image(self.pimg)
def run(self):
self.pippo.mainloop()
if __name__ == "__main__":
path = 'example.png'
app = main(path)
app.run()
don't know about your question though, but wanted to be sure your starting example works right. Let me know if it could be related to python/pillow/tkinter version or something else
Here my window image results before ad after pressing fix button :
At the end found out that your code does work as long as you use
self.root.attributes('-zoomed', True) instead of `self.root.state("zoomed")`
The problem is here. self.root.winfo_screenwidth()
Change it to self.cv.width. I don't know why.
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
self.cv.create_image(self.root.winfo_screenwidth()/2,
self.root.winfo_screenheight()/2, image=self.img, tags=('all'))
Change the last line to
self.cv.create_image(self.cv.width/2,
self.cv.height/2, image=self.img, tags=('all'))
Fixes the issue.
Tk.winfo_screenwidth() according to https://tkdocs.com/shipman/universal.html returns the width of the screen, indepedant of the size of the window, so even if you have a small window on a 1920x1080 display, this function will return 1920.
self.cv.width returns the width of the canvas object.

Get access to a variable method to end threading loop?

I have question about how to call some variables, in this case from the method inside exportCsv that belongs to back_Gui class. I want to use the variables self.msg_ and self.opstat in the method __update from class _Gui to stop the reproduction of the gift and promt out the window to save the file. When I run the code and press the button it iterates in an infinite loop because the variable is not passing to. Also, I try to aggregate some threading to not to freeze the window when the button is pressed. Any solution?.
Also, I think these variables I mention would be inside the try but what happens in the except?. Do I have to create more variables to avoid infinite looping on the gift?
from tkinter import *
import tkinter as tk
from tkinter import filedialog
import sqlite3
import pandas as pd
from PIL import Image, ImageTk
import time
import threading
class back_Gui: #Superclass
'''Class that handle the data'''
def __init__(self, db_name = 'database.db'):
self.db_name = db_name
self.msg_ = None
self.opstat = None
.
.
.
def exportCSV(self):
df_to = self.df
try:
export_file = filedialog.asksaveasfilename(defaultextension='.csv')
df_to.to_csv(export_file, index=False, header=True)
except:
pass
#These are the variables that I need
self.msg_ = "Done."
self.opstat = -1
class _Gui(back_Gui): #Subclass
def __init__(self, window):
'''Gui of the windw tk '''
self.wind = window #child
super().__init__()
self.text_font = ('Helvetica', '10', 'bold')
self.Button_(self.wind)
def Button_(self, wind):
"""Run the gift while the csv is being generated"""
#Button
b1 = Button(self.wind, text="random",
font=self.text_font,
command=self.job_genCsv,
).grid(row=1, column=5, padx=5, pady=5 ,sticky=W)
def frame_maps(self, wind):
'''Frame Containter'''
self.frame = LabelFrame(self.wind, text='Hail Hydra', font=self.text_font, height = 500, width = 1300, bd=4)
self.frame.grid(row=2, columnspan=20, sticky=W+E)#, columnspan=3, padx=25, pady=25)
# create the canvas, size in pixels
self.canvas = Canvas(self.frame, width=1300, height=500, bg='white')
# pack the canvas into a frame/form
self.canvas.grid(row=0, columnspan=20, sticky=N, ipadx=20, ipady=20)#
# load the .gif image file
#Here it has to be use the self because is local variable
self.current_image_number = 0
file="cain.gif"
info = Image.open(file)
self.frames = info.n_frames
self.gif1 = [PhotoImage(file=file, format=f"gif -index {i}") for i in range(self.frames)]
def __update(self):
#self.job_genCsv()
''' update the gift '''
self.frame.update_idletasks()#update
if self.opstat >= 0.0:
#msg = self.image_on_canvas #
# next image
self.current_image_number += 1
# return to first image
if self.current_image_number == self.frames: #len(self.images):
self.current_image_number = 0
# change image on canvas
self.canvas.itemconfig(self.update, image=self.gif1[self.current_image_number])
_ = threading.Timer(0, self.__update).start()
print("loop")
else:
if self.msg_ == "Done.":
self.update = self.canvas.create_image(250, 50, anchor=NW, image=self.gif1[self.current_image_number])
del self.msg_
#control variable restablished
self.opstat = 0
print("ends")
def job_genCsv(self):
'''Runs his job and call frame_maps '''
self.frame_maps(self)
_ = threading.Timer(0, self.exportCSV).start()
_ = threading.Timer(0, self.__update).start()
if __name__ == '__main__':
window = Tk()
application = _Gui(window)
window.mainloop()
I'm getting this error:
File "C:\Users\Documents\run.py", line 214, in __update
if self.opstat >= 0.0:
AttributeError: '_Gui' object has no attribute 'opstat'

performing operations on images uploaded by tkinter

I am new to Tkinter and python. I am trying to upload two images and then perform some operations on them. The problem is that the Window class is loading all at once or the code in running parallel, so the images uploaded after that have been already assigned to None since they were uploaded later in the ScrollableFrame class and did not have a value earlier.
from tkinter import *
from tkinter import filedialog
from PIL import Image, ImageTk, ImageOps
import os
# ************************
# Scrollable Frame Class
# ************************
row=0
column=0
imagePaths = []
#Class to generate a frame to add to the GUI with vertical and horizontal scroll bars
class ScrollableFrame(Frame):
#The Constructor method for the class
def __init__(self, parent , *args, **kw):
Frame.__init__(self, parent, *args, **kw)
#Defining the position of the frame grid
self.grid(row = row , column = column)
self.image = None
self.imageFile = None
#Defining the vertical scroll bar
vscrollbar = Scrollbar(self, orient=VERTICAL)
vscrollbar.grid(row=row, column=column+1, sticky=N+S)
#Defining the horizontal scroll bar
hscrollbar = Scrollbar(self, orient = 'horizontal')
hscrollbar.grid(row=row+1, column=column, sticky=E+W)
#Defining the canvas to put the scroll bars on
canvas = Canvas(self, bd=0, highlightthickness=0, yscrollcommand=vscrollbar.set, xscrollcommand=hscrollbar.set)
canvas.grid(row=row, column=column, sticky = N+S+E+W)
canvas.config( width=800, height = 800 )
#Defining the scrolling commands (vertically and horizontally )
vscrollbar.config(command=canvas.yview)
hscrollbar.config(command=canvas.xview)
#Defining the scroll region where the scrolling is active
canvas.config(scrollregion= (0,0,1280,1024))
self.canvas = canvas
def openImage(self):
#Getting the path of the image
imageFile = filedialog.askopenfilename(initialdir=os.getcwd(),title="Select BMP File",filetypes=[("BMP Files",("*.bmp",".png",".jpg",".jpeg",".tif",".tiff"))])
#Assigning the image value to this frame object
self.imageFile = imageFile
if not imageFile:
return
def showImage(self):
#Getting the path of the image
imageFile = filedialog.askopenfilename(initialdir=os.getcwd(),title="Select BMP File",filetypes=[("BMP Files",("*.bmp",".png",".jpg",".jpeg",".tif",".tiff"))])
#Assigning the image value to this frame object
self.imageFile = imageFile
if not imageFile:
return
#Checking for the extension of the image
filename, file_extension = os.path.splitext(imageFile)
#If it is a .bmp, this means that it is an HD image, where we can directly display it
if file_extension == '.bmp':
imageToDisplay = Image.open(imageFile)
#border = (0, 0, 0, 66) #Decide on the area you want to crop in terms of no. pixels: left, up, right, bottom
#ImageOps.crop(imageToDisplay, border)
img = ImageTk.PhotoImage(imageToDisplay)
self.image = img
#print ("Done conversion")
self.canvas.create_image(row, column, image=self.image, anchor=NW)
class Window(Frame):
def __init__(self, master=None):
global row, column,imagePaths
Frame.__init__(self, master)
self.master = master
self.pos = []
self.master.title("BMP Image GUI")
self.pack(fill=BOTH, expand=1)
self.label = Label(self, text="Instructions: \n 1. Open the HD image. \n 2. Open the EBSD image. \n 3. Open the Color Map image.", anchor=W, justify=LEFT)
self.label.place(x=1640, y=0)
menu = Menu(self.master)
self.master.config(menu=menu)
self.frame1 = ScrollableFrame(self)
row=0
column=1
self.frame2 = ScrollableFrame(self)
# File Bar
file = Menu(menu)
file.add_command(label="Open HD image", command=self.frame1.showImage)
img = Image.open("original.bmp")
HD = self.frame2.imageFile
file.add_command(label="Open EBSD image", command=self.frame2.openImage)
EBSD = self.frame2.imageFile
print (HD)
print (EBSD)
root = tk.Tk()
root.geometry("%dx%d" % (1670, 1024))
root.title("BMP Image GUI")
app = Window(root)
app.pack(fill=tk.BOTH, expand=1)
#print (HD)
root.mainloop()
So printing the HD and EBSD images is giving None. What I am aiming to to make them get the actual value assigned after the upload.
This is a lot of code and it doesn't run. It also has a few problems. When you are coding complex applications it is best to do it one little piece at a time or you'll have problems finding the problems. Here are a few:
Don't use global variables in an object oriented application. The names row, column and imagePaths should belong to either of the two classes.
The menu doesn't work because you have not implemented it correctly:
file = Menu(menu)
menu.add_cascade(label='File', menu=file) # You need this for it to work
file.add_command(label="Open HD image", command=self.frame1.showImage)
# etc...
You are packing app twice, once in it's __init__() function and once after it's been created (in the global scope).
The scrollable frames are packed in front of the Label with instructions so you can't see it.
Try fixing these problems by writing components, and when each component works then combine them. If there is a problem with any of the components, or if everything works but for one thing, come back here and we will be able to give you a better answer.

Why tkinter canvas doesn't update when used in a class

I am writing a small program, where I want to draw something on canvas. This code works for me;
import tkinter as tk
from PIL import Image, ImageTk
from l_systems import Lindenmayer
if __name__ == "__main__":
root = tk.Tk()
root.title("Draw Shapes with L-Equations")
cv = tk.Canvas(width=600, height=600, bg='white')
cv.pack()
image1 = Image.new("RGB", (600, 600), (255,255,255))
koch = Lindenmayer(image1)
koch.init(
iterations = 6,
angle = 25,
axiom = "---X",
rules = {"X":"2F-[1[X]+3X]4+F[3+FX]-X", "F":"FF"},
constants = "X") # This creates a drawing on PIL image
# Canvas.create_image expects a PhotoImage
photo = ImageTk.PhotoImage(image1)
cv.create_image((300,300), image=photo)
root.mainloop()
However, I want to organize my tkinter application as a class, therefore I have tried this code,
class main(tk.Frame):
w = 600
h = 600
def __init__(self,parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.cv = tk.Canvas(width=self.w, height=self.h, bg='white')
self.cv.pack()
self.render_image()
def render_image(self):
image1 = Image.new("RGB", (self.w, self.h), (255,255,255))
koch = Lindenmayer(image1)
koch.init(
iterations = 6,
angle = 25,
axiom = "---X",
rules = {"X":"2F-[1[X]+3X]4+F[3+FX]-X", "F":"FF"},
constants = "X"
)
photo = ImageTk.PhotoImage(image1)
self.cv.create_image((self.w/2,self.h/2), image=photo)
if __name__ == "__main__":
root = tk.Tk()
root.title("Draw Shapes with L-Equations")
app = main(root).pack()
root.mainloop()
In this second case, I don't see any drawing on canvas. It is just a white background. How can I fix this?
PhotoImage can have problem in classes and functions. Garbage collector can remove it from memory.
EDIT:
I could check this (because I have to Lindenmayer module)
but your class could look this:
Almost everything is in class.
Class names should normally use the CapWords convention. - see PEP 8 -- Style Guide for Python Code. Event SO use that rule to recognize classes in code and use light blue color.
import tkinter as tk
from PIL import Image, ImageTk
from l_systems import Lindenmayer
class Main(tk.Frame):
def __init__(self,parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.w = 600
self.h = 600
self.parent = parent
self.parent.title("Draw Shapes with L-Equations")
self.cv = tk.Canvas(width=self.w, height=self.h, bg='white')
self.cv.pack()
self.render_image()
self.parent.pack()
def render_image(self):
image1 = Image.new("RGB", (self.w, self.h), (255,255,255))
koch = Lindenmayer(image1)
koch.init(
iterations = 6,
angle = 25,
axiom = "---X",
rules = {"X":"2F-[1[X]+3X]4+F[3+FX]-X", "F":"FF"},
constants = "X"
)
self.photo = ImageTk.PhotoImage(image1)
self.cv.create_image((self.w/2,self.h/2), image=self.photo)
def run(self):
self.parent.mainloop()
if __name__ == "__main__":
Main(tk.Tk()).run()

Categories

Resources