How do I get my program to update this tkinter label in my nested loop so that it shows the grayscale process? - python-3.x

So I made a program that converts an image to grayscale. I first used graphics.py from a Zelle's Python Programming An Introduction To Computer Science book. I believe this is based on tkinter. I finished the program using a nested loop to iterate through a .gif picture and grab each individual pixel, replacing it with a grayscale algorithm. I had the change happen in a loop and it gave it an effect that shows the grayscale happening across the window. This was slow but it looked neat, but I realized I wanted to be able to use more file types. I found PIL for Python 3.3 and tkinter, using PIL to open the images, turning them into a tkinter PhotoImage, than displaying them in a tkinter window. Now my program will show the image before being processed and after, I would simply like to see the program update the image in the loop so that it shows the grayscale process. Any help would be great appreciated.
Here is my code:
from PIL import Image, ImageTk
from graphics import GraphWin
import tkinter
window = tkinter.Tk()
window.title('# Grayscale')
def GrayScaleConvertor():
#Opens image, creates window and draws image
picToConvert = 'sea.jpg'
pic = Image.open(picToConvert)
picWidth, picHeight = pic.size
# Treats the image as a 2d array, iterates through changing the
#values of each pixel with the algorithm for gray
tkPic = ImageTk.PhotoImage(pic, master = window)
label1 = tkinter.Label(window, image = tkPic)
rgbList = pic.load() #Get a 2d array of the pixels
for row in range(picWidth):
for column in range(picHeight):
rgb = rgbList[row,column]
r,g,b = rgb # Unpacks the RGB value tuple per pixel
grayAlgorithm1 = (r+g+b) // 3
rgbList[row,column] = (grayAlgorithm1, grayAlgorithm1, grayAlgorithm1)
# Converting to a tkinter PhotoImage
tkPic1 = ImageTk.PhotoImage(pic, master = window)
label2 = tkinter.Label(window, image = tkPic1)
# Draws the images to the window
label1.pack() # The image before grayscale
label2.pack() # The image after being grayscaled
window.mainloop()
GrayScaleConvertor()

You could use after(time, function_name) to call function (for example) every 10ms and change one (or more) pixel.
pseudocode:
tkinter.after(10, change_one_pixel)
def change_one_pixel():
change_next_pixel() # if you change more pixels you get faster animation
create_new_image()
update_label()
if any_pixel_left:
tkinter.after(10, change_one_pixel)
EDIT:
Full working code
#import Tkinter as tk # Python 2.7.x
import tkinter as tk # Python 3.x
from PIL import Image, ImageTk
class GrayScaleConvertor():
def __init__(self):
self.window = tk.Tk()
self.window.title('# Grayscale')
#Opens image, creates window and draws image
picToConvert = 'sea.jpg'
#picToConvert = 'background.jpg'
self.pic = Image.open(picToConvert)
# Treats the image as a 2d array, iterates through changing the
#values of each pixel with the algorithm for gray
self.tkPic1 = ImageTk.PhotoImage(self.pic, master = self.window)
self.label1 = tk.Label(self.window, image = self.tkPic1)
# Converting to a tkinter PhotoImage
self.tkPic2 = ImageTk.PhotoImage(self.pic, master = self.window)
self.label2 = tk.Label(self.window, image = self.tkPic2)
# Draws the images to the window
self.label1.pack() # The image before grayscale
self.label2.pack() # The image after being grayscaled
self.column = 0 # start column
self.step = 10 # number columns in one step
self.window.after(1, self.change_pixel) # 1ms
def run(self):
self.window.mainloop()
def change_pixel(self):
rgbList = self.pic.load() #Get a 2d array of the pixels
picWidth, picHeight = self.pic.size
# not to leave image
if self.column + self.step > picWidth:
self.step = picWidth - self.column
# change columns
for column in range(self.column, self.column+self.step):
for row in range(picHeight):
rgb = rgbList[column,row]
r,g,b = rgb # Unpacks the RGB value tuple per pixel
grayAlgorithm1 = (r+g+b) // 3
rgbList[column,row] = (grayAlgorithm1, grayAlgorithm1, grayAlgorithm1)
# change image in label
if self.tkPic2:
del self.tkPic2
self.tkPic2 = ImageTk.PhotoImage(self.pic, master = self.window)
self.label2.config(image = self.tkPic2)
# move start column
self.column += self.step
# if still are columns - call again
if self.column < picWidth:
print "change image"
self.window.after(1, self.change_pixel)
else:
print "the end"
GrayScaleConvertor().run()

Related

display video camera on two different windows tkinter python

i have a GUI python application that contain two different function windows and i'am trying to display video camera in the second window of tkinter application.
But, the video frame always be showing on the first windows!! i don't know if it is a problem of threads, or camera librairy. is there any fault in my code?
thanks in advance.
# add the necessairy librairy
import tkinter as tk
import threading
import time
import subprocess
from imutils.video import VideoStream
import time
import imutils
import cv2
import argparse
from PIL import Image
from PIL import ImageTk
class PhotoBoothApp:
def __init__(self,vs):
# store the video stream object and output path, then initialize
# the most recently read frame, thread for reading frames, and
# the thread stop event
self.vs = vs
self.frame = None
self.thread = None
self.stopEvent_2 = None
# initialize the root window and image panel
self.root = tk.Tk()
self.panel = None
self.user_input = tk.StringVar(self.root)
self.but_frame = tk.Frame(self.root)
w = 800 # width for the Tk root
h = 500 # height for the Tk root
# get screen width and height
ws = self.root.winfo_screenwidth() # width of the screen
hs = self.root.winfo_screenheight() # height of the screen
# calculate x and y coordinates for the Tk master window
x = (ws/3) - (w/3)
y = (hs/3) - (h/3)
# set the dimensions of the screen
# and where it is placed
self.root.geometry('%dx%d+%d+%d' % (w, h, x, y))
# when open the second window i want it to be on toplevel; it means when i click outside the frame it won't get hide
self.root.title("Test thread")
# create a button, that when pressed, will take the current
# frame and save it to file
btn = tk.Button(self.but_frame, bd = '5',text=" photo ")
self.but_frame.pack(side="left")
# start a thread that constantly pools the video sensor for
# the most recently read frame
self.stopEvent_2 = threading.Event()
self.thread = threading.Thread(target=self.videoLoop, args=())
self.thread.start()
# set a callback to handle when the window is closed
self.root.wm_protocol("WM_DELETE_WINDOW", self.onClose)
def videoLoop(self):
# DISCLAIMER:
# I'm not a GUI developer, nor do I even pretend to be. This
# try/except statement is a pretty ugly hack to get around
# a RunTime error that Tkinter throws due to threading
try:
# keep looping over frames until we are instructed to stop
while not self.stopEvent_2.is_set():
# grab the frame from the video stream and resize it to
# have a maximum width of 300 pixels
self.frame = self.vs.read()
self.frame = imutils.resize(self.frame, width=300)
#cv2.imshow('video player', self.frame)
# OpenCV represents images in BGR order; however PIL
# represents images in RGB order, so we need to swap
# the channels, then convert to PIL and ImageTk format
image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
image = Image.fromarray(image)
image = ImageTk.PhotoImage(image)
# if the panel is not None, we need to initialize it
if self.panel is None:
self.panel = tk.Label(image=image)
self.panel.image = image
self.panel.pack(side="left", padx=10, pady=10)
# otherwise, simply update the panel
else:
self.panel.configure(image=image)
self.panel.image = image
except RuntimeError :
print("[INFO] caught a RuntimeError_________________________________________")
def onClose(self):
# set the stop event, cleanup the camera, and allow the rest of
# the quit process to continue
print("[INFO] closing...")
self.stopEvent_2.set()
self.vs.stop()
self.root.quit()
# here's the main window
Mafenetre = tk.Tk()
#set main window title
Mafenetre.title("GUI")
Mafenetre['bg']='white' # couleur de fond
# get screen width and height
wf1= Mafenetre.winfo_screenwidth()
hf1= Mafenetre.winfo_screenheight()
A = str(wf1)
B = str(hf1)
# set the dimensions of the screen
# and where it is placed
w = 500 # width for the Tk root
h = 500 # height for the Tk root
# get screen width and height
ws = Mafenetre.winfo_screenwidth() # width of the screen
hs = Mafenetre.winfo_screenheight() # height of the screen
# calculate x and y coordinates for the Tk master window
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
# set the dimensions of the screen
# and where it is placed
Mafenetre.geometry('%dx%d+%d+%d' % (w, h, x, y))
# add test button to the main window
reglage = tk.Button(Mafenetre, bd = '5',text=" PARAM ", bg='#c42034', fg='white',font= "Helvetica", 12))
reglage.pack(side=tk.TOP, padx=6,pady=35)
camera = VideoStream(0).start()
pba = PhotoBoothApp(camera)
Mafenetre.mainloop()

Tkinter resizing images in labels arranged in a grid

To start, I'm totally new to Tkinter and am trying to make a Raspberry Pi Media Player of sorts...
I grab the directories of all .mp4 files on a USB drive, and use PIL to put the thumbnails of the videos into a 3x3 grid of labels, with the grid inside of a frame (frame2 in the code).
Right now, with the thumbnails of varying sizes, the labels are also inconsistently sized. Also, only the top-right portion of larger thumbnails are displayed, rather than the entire image.
How can I to scale and fit the thumbnails into consistently sized labels, in grid form?
Here is part of my code (It's quite large so I try to include only the relevant parts):
import tkinter as tk
from subprocess import Popen
from time import sleep
import os
from random import randint
import imageio
from PIL import ImageTk, Image
from pathlib import Path
#putting 100th frame of video with 'path' into the label
def pack_thumbnail(path, label):
#this is probably not a good way to do this
video = imageio.get_reader(path)
for i in range(100):
try:
image = video.get_next_data()
except:
video.close()
break
frame_image = ImageTk.PhotoImage(Image.fromarray(image))
label.config(image=frame_image)
label.image = frame_image
window = tk.Tk()
window.attributes("-fullscreen", True)
frame1 = tk.Frame(master=window, width=200, height=100, bg="white")
frame1.pack(fill=tk.Y, side=tk.LEFT)
#frame2 contains the grid of labels
frame2 = tk.Frame()
for i in range(3):
frame2.columnconfigure(i, weight=1, minsize=75)
frame2.rowconfigure(i, weight=1, minsize=50)
for j in range(0, 3):
frame = tk.Frame(master=frame2, relief=tk.RAISED, borderwidth=1)
frame.grid(row=i, column=j, padx=5, pady=5)
#path to video to get thumbnail (i only have 3 videos so i randomize it)
vid_path=f"/media/pi/{os.listdir('/media/pi/')[0]}/{folder_name}/{videos[randint(0, 2)]}"
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
pack_thumbnail(vid_path, label)
label.pack(padx=5, pady=5)
frame2.pack()
window.bind("<Escape>", lambda x: window.destroy())
window.mainloop()
You can use Image.thumbnail() to resize the image:
# putting 100th frame of video with 'path' into the label
def pack_thumbnail(path, label):
with imageio.get_reader(path) as video:
image = video.get_data(100) # use get_date() instead of get_next_data()
w, h = 200, 200 # thumbnail size
image = Image.fromarray(image)
image.thumbnail((w, h)) # resize the image
frame_image = ImageTk.PhotoImage(image)
label.config(image=frame_image, width=w, height=h)
label.image = frame_image

How to convert ImageTk to Image?

Let's say I have some ImageTk.PhotoImage image stored in the variable imgtk. How can I convert it back to an Image.Image?
The reason is that I want to resize it, but it seems that .resize() only works for Image.Images.
I know it is awfully late, but I just came across the same issue and I just discovered that there is a getimage(imagetk) function in the ImageTk interface.
So, to get your imgtk back as an PIL Image you can do:
img = ImageTk.getimage( imgtk )
I just did a quick test on Windows (Python 3.8.5/Pillow 8.1.2/Tkinter 8.6) and it seems to work fine:
# imgtk is an ImageTk.PhotoImage object
img = ImageTk.getimage( imgtk )
img.show()
img.close()
Ok, that was not easy but I think I have a solution though you need to go into some private methods of label.image. Maybe there is a better way if so I would love to see.
import tkinter as tk
from tkinter import Label
import numpy as np
from PIL import Image, ImageTk
root = tk.Tk()
# create label1 with an image
image = Image.open('pic1.jpg')
image = image.resize((500, 750), Image.ANTIALIAS)
picture = ImageTk.PhotoImage(image=image)
label1 = Label(root, image=picture)
label1.image = picture
# extract rgb from image of label1
width, height = label1.image._PhotoImage__size
rgb = np.empty((height, width, 3))
for j in range(height):
for i in range(width):
rgb[j, i, :] = label1.image._PhotoImage__photo.get(x=i, y=j)
# create new image from rgb, resize and use for label2
new_image = Image.fromarray(rgb.astype('uint8'))
new_image = new_image.resize((250, 300), Image.ANTIALIAS)
picture2 = ImageTk.PhotoImage(image=new_image)
label2 = Label(root, image=picture2)
label2.image = picture2
# grid the two labels
label1.grid(row=0, column=0)
label2.grid(row=0, column=1)
root.mainloop()
Actually you can zoom and reduce the original picture by using the methods zoom to enlarge the picture (zoom(2) doubles the size) and subsample to reduce the size (subsample(2) halves the picture size).
for example
picture2 = label1.image._PhotoImage__photo.subsample(4)
reduces the size of the picture to a quarter and you can skip all the conversion to an Image.
According to label1.image._PhotoImage__photo.subsample.__doc__:
Return a new PhotoImage based on the same image as this widget but use only every Xth or Yth pixel. If y is not given, the default value is the same as x
and label1.image._PhotoImage__photo.zoom.__doc__:
Return a new PhotoImage with the same image as this widget but zoom it with a factor of x in the X direction and y in the Y direction. If y is not given, the default value is the same as x.

How to enhance window size selection on a tkinter project including button-image as label?

I'm currently working on a little project on python-3.x including some tkinter ressources. My program is made to display on a screen a list of pictures included in a directory, each picture is put on a button that is a sixth of the original image, and if we click on it, it display the image on his original size on a new window. The original window is set by the amount of pictures i put in the columns (i can choose in the code) and i ve made a scrollbar because i have to work with a lot of pictures.
But here is my problem, it's works fine except that if i change the window size, like reduce it for example, the buttons don't follow, they just vanish behind the window, and with the scrollbar.
I'm not particularly good in python so i was wondering that maybe by doing like a threading we could get the window size in live and then if the window size is inferior/superior of our columns of buttons, we could resize it and change the amount of columns then reload the page, but i will have to work with multiple image so it will take a lot of time.
from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *
from PIL import Image, ImageTk
import tkinter as tk
import glob
import os
import cv2
import copy
import _thread
import time
folder = 'X:/users/Robin/data/dataset-valid/visu/*.jpg'
a=glob.glob(folder)
fic = "../data/list.txt"
fichObj=open(fic,"w")
p = []
for f in a:
fichObj.write(f+"\n")
fichObj.close()
class SuperPhoto(object):
def __init__(self, photo , image):
self.photo = photo
temp = cv2.resize(image, (int((self.photo.width())/6) , int((self.photo.height())/6)))
red = temp[:,:,2].copy()
blue = temp[:,:,0].copy()
temp[:,:,0] = red
temp[:,:,2] = blue
temp = Image.fromarray(temp)
self.miniature = ImageTk.PhotoImage(temp)
def agrandir(self):
Newfen=Toplevel()
Newfen.geometry("+60+60")
#self.photo.resize((500,500))
print(type(self.photo))
label = Label(Newfen, image=self.photo, width=self.photo.width(), height=self.photo.height())
label.image = self.photo # keep a reference!
label.pack()
if os.path.exists (fic): #os.path utile
count = len(open(fic).readlines())
print(count)
#lin = open(fic).readlines()
#print(lin)
class ScrollableCanvas(Frame):
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
canvas=Canvas(self,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))
canvas.update_idletasks()
vbar=Scrollbar(self,orient=VERTICAL)
vbar.pack(side=RIGHT, fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=1200,height=700)
canvas.config(yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=True,fill=BOTH)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior, anchor=NW )
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(event):
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the inner frame's width to fill the canvas
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
canvas.bind('<Configure>', _configure_canvas)
class Main_frame(Frame):
# Init
def __init__(self, fenetre_principale=None):
Frame.__init__(self, fenetre_principale)
self.grid()
self.scrollable_canvas = ScrollableCanvas(self)
self.scrollable_canvas.grid(row=1,column=1)
nbCol = 4
for file in a:
image = Image.open(file)
photo = ImageTk.PhotoImage(image)
w = photo.width()
L.append(int(w/6))
#print(L)
sumL = int(sum(L)/nbCol)
print(sumL)
p.append(SuperPhoto(photo, cv2.imread(file)))
for ligne in range(int(count/nbCol)):
for colonne in range(nbCol):
photo = p[ligne * nbCol + colonne]
button = Button(self.scrollable_canvas.interior, image=photo.miniature, command=photo.agrandir)
button.grid(row=ligne, column=colonne)
if __name__ == "__main__":
root = Tk()
root.title("VISU")
root.geometry("+0+0")
L= []
interface = Main_frame(fenetre_principale=root)
root.update_idletasks()
print(root.winfo_width())
print(root.geometry())
interface.mainloop()
So, I except this program to work like a classic directory display, with the columns that change automatically when we resize the window and with the scrollbar that follow it.
If you have any solutions it will really help me ..
You can try it, just put some jpeg pictures in a directory and change the folder variable with the link of your directory.
Thanks in advance for your help, if you have any questions to understand more clearly what i've said don't hesitate.
Each time the root window is resized, a <Configure> event is triggered. Catch it as follows:
def resize(event):
root.update_idletasks()
#update all image sizes here if needed
#all widgets can be 're-grided' here based on new width and height of root window
root.bind('<Configure>', resize)
If you want to ensure that your window cannot be resized, use the following:
root.resizable(False, False)

Display image based on a output in one window (Python)

I have an implementation of k-NN algorithm in Python, that returns the class label of an input. What I need is to show an image assigned to the class label in one window while output is coming (by refreshing the window). Problem is, I am not very experienced in GUI programming, thus I need some resources and assistance to start with. What libraries, books and tutorials could you recommend? Pieces of code would be appreciated too.
There are many libraries allow you add images to your program such as Turtle, PIL.ImagTk and Canvas and you can even use Pygame to a best performance.
PIL.ImagTk and Canvas:
from Tkinter import *
from PIL import ImageTk
backgroundImage = PhotoImage("image path (gif/PPM)")
canvas = Canvas(width = 200, height = 200, bg = 'blue')
canvas.pack(expand = YES, fill = BOTH)
image = ImageTk.PhotoImage(file = "C:/Python27/programas/zimages/gato.png")
canvas.create_image(10, 10, image = image, anchor = NW)
mainloop()
Turtle:
import turtle
screen = turtle.Screen()
# click the image icon in the top right of the code window to see
# which images are available in this trinket
image = "rocketship.png"
# add the shape first then set the turtle shape
screen.addshape(image)
turtle.shape(image)
screen.bgcolor("lightblue")
move_speed = 10
turn_speed = 10
# these defs control the movement of our "turtle"
def forward():
turtle.forward(move_speed)
def backward():
turtle.backward(move_speed)
def left():
turtle.left(turn_speed)
def right():
turtle.right(turn_speed)
turtle.penup()
turtle.speed(0)
turtle.home()
# now associate the defs from above with certain keyboard events
screen.onkey(forward, "Up")
screen.onkey(backward, "Down")
screen.onkey(left, "Left")
screen.onkey(right, "Right")
screen.listen()
Now let’s change the background
import turtle
screen = turtle.Screen()
# this assures that the size of the screen will always be 400x400 ...
screen.setup(400, 400)
# ... which is the same size as our image
# now set the background to our space image
screen.bgpic("space.jpg")
# Or, set the shape of a turtle
screen.addshape("rocketship.png")
turtle.shape("rocketship.png")
move_speed = 10
turn_speed = 10
# these defs control the movement of our "turtle"
def forward():
turtle.forward(move_speed)
def backward():
turtle.backward(move_speed)
def left():
turtle.left(turn_speed)
def right():
turtle.right(turn_speed)
turtle.penup()
turtle.speed(0)
turtle.home()
# now associate the defs from above with certain keyboard events
screen.onkey(forward, "Up")
screen.onkey(backward, "Down")
screen.onkey(left, "Left")
screen.onkey(right, "Right")
screen.listen()
for Turtle:
http://blog.trinket.io/using-images-in-turtle-programs/

Resources