I'm trying to write a script that will allow the user to select a folder with images and then save the coordinates of the user's clicks on each image. To do this I would like to display each image file on a Tkinter canvas, save click locations, then when the canvas is closed, open the next image.
I can get this to work for a single image with the code below (adapted from this question). I was hoping the for File in imgs loop would keep opening the next image, but it does not. I suspect I need a on_closing function to tell Tkinter to open another image.
What's the proper way to have Tkinter open the next image after closing an image?
from Tkinter import *
from tkFileDialog import askopenfilenames, askopenfilename, askdirectory
from PIL import Image, ImageTk
import cv2
import numpy as np
import os
if __name__ == "__main__":
root = Tk()
#setting up a tkinter canvas with scrollbars
frame = Frame(width=1920, height=1080, bd=2, relief=SUNKEN)
frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(0, weight=1)
xscroll = Scrollbar(frame, orient=HORIZONTAL)
xscroll.grid(row=1, column=0, sticky=E+W)
yscroll = Scrollbar(frame)
yscroll.grid(row=0, column=1, sticky=N+S)
canvas = Canvas(frame, bd=0, xscrollcommand=xscroll.set, yscrollcommand=yscroll.set)
canvas.config(width=1920, height=1080)
canvas.grid(row=0, column=0, sticky=N+S+E+W)
xscroll.config(command=canvas.xview)
yscroll.config(command=canvas.yview)
frame.pack(fill=BOTH,expand=1)
# Function to be called when mouse is clicked
def save_coords(event):
coords.append([event.x, event.y])
# def on_closing():
# Open the next image file
# Create empty list for coordinate arrays to be appended to
coords = []
# Choose multiple images
img_dir = askdirectory(parent=root, initialdir="D:/Temp/", title='Choose folder')
os.chdir(img_dir)
imgs = os.listdir(img_dir)
#imgs = askopenfilenames(parent=root, initialdir="D:/Temp/cvCal/", title='Choose images')
for File in imgs:
img = ImageTk.PhotoImage(Image.open(File).resize((1280,720), Image.ANTIALIAS))
canvas.create_image(0,0,image=img,anchor="nw")
canvas.config(scrollregion=canvas.bbox(ALL))
canvas.bind("<Button 1>",save_coords)
# on_closing()...
root.mainloop()
It would be a lot easier if you can use a Label instead:
Also, note there is no reason to use ImageTk since loading an image from a file is built into tkinter as Tkinter.PhotoImage.
Also, I converted your wildcard import to a normal import; wildcard imports are messy and against PEP8.
Lastly, I don't know what you mean with 'on closing', so I added a button to advance to the next image. This will throw a StopIteration error on the last image that you will need to handle.
import Tkinter as tk
from tkFileDialog import askdirectory
import os
# Create empty list for coordinate arrays to be appended to
coords = []
# Function to be called when mouse is clicked
def save_coords(event):
click_loc = [event.x, event.y]
print "you clicked on", click_loc
coords.append(click_loc)
# Function to load the next image into the Label
def next_img():
img_label.img = tk.PhotoImage(file=next(imgs))
img_label.config(image=img_label.img)
root = tk.Tk()
# Choose multiple images
img_dir = askdirectory(parent=root, initialdir="D:/Temp/", title='Choose folder')
os.chdir(img_dir)
imgs = iter(os.listdir(img_dir))
img_label = tk.Label(root)
img_label.pack()
img_label.bind("<Button-1>",save_coords)
btn = tk.Button(root, text='Next image', command=next_img)
btn.pack()
next_img() # load first image
root.mainloop()
print coords
Related
A part of the input image is cut off when displaying via canvas.create_image in a tkinter window
I have tried to change the "side" parameters and the width / height values in the canvas.create_image object.
here is my code and input image:
import tkinter as tk
from tkinter import *
from tkinter.filedialog import askopenfilename
import os
from PIL import ImageTk, Image
def show_values():
print(slider1.get())
window = tk.Tk()
filename = askopenfilename() # show an "Open" dialog box and return the path to the selected file
print(filename)
slider1 = Scale(window, from_=0, to=42, orient='vertical')
slider1.pack(side=LEFT)
canvas = Canvas(window)
canvas.pack()
img = ImageTk.PhotoImage(Image.open(filename))
imageWidth = img.width()
imageHeight = img.height()
canvas.create_image(imageWidth + 1, imageHeight + 1, image=img)
canvas.pack(side=RIGHT)
Button(window, text='Process Image', command=show_values).pack(side=BOTTOM)
window.mainloop()
The root of the problem is that, by default, images are placed on a canvas with the center of the image at the coordinates given. You're giving coordinates based on the image width and height, and that is where the center is going.
For example, the image in the question is 297x170. You're using those as the coordinates for the image, which means that the center of the image will be at 297x170. The canvas is roughly 300x200. Since the center of the image is at x=297, it's going to extend beyond the right and bottom edges of the canvas.
It's not clear where you want the image to appear, but a simple fix to illustrate how anchor affects placement is to put the image at 0,0 and set the anchor to "nw" (northwest). That will show the entire image. If you want the image centered in the canvas, the solution just involves a little math to compute the coordinates of the center of the canvas.
An answer would be to not use Canvas whatsoever and instead use a frame and label to display the image. Here is a working example:
import tkinter as tk
from tkinter import *
from tkinter.filedialog import askopenfilename
import os
from PIL import ImageTk, Image
def show_values():
print(slider1.get())
window = tk.Tk()
filename = askopenfilename() # show an "Open" dialog box and return the path to the selected file
print(filename)
filename3, file_extension = os.path.splitext(filename)
slider1 = Scale(window, from_=0, to=42, orient='vertical')
slider1.pack(side=LEFT)
frame = Frame(window, width=600, height=400)
frame.pack()
img = ImageTk.PhotoImage(Image.open(filename))
imageWidth = img.width()
imageHeight = img.height()
label = Label(frame, image = img)
label.pack(side=RIGHT)
Button(window, text='Process Image', command=show_values).pack(side=BOTTOM)
window.mainloop()
I am very new to python and want to build a gui that lets you skip through different pdfs. I managed to display the first pdf. My problem now is that I could not manage to display the second pdf and all following. If I only repeat the command to display the pdf, the new pdf gets displayed next to the old one, instead of replacing it. I have been through several hours of extensive googling and could not find out how to solve this. Can someone maybe help?
Here's my code:
from tkinter import *
import tkinter as tk
import glob
from tkPDFViewer import tkPDFViewer as pdf
from tkdocviewer import *
parent_path = 'somepath\\'
doc_list = glob.glob((parent_path + "*//*.pdf"))
doc_counter = 0
root = tk.Tk()
root.title('Training Data Creator')
root.geometry("1000x1000")
frame_r = Frame(root, relief=RAISED, borderwidth=1)
frame_r.pack(fill=BOTH, expand=True, side=tk.LEFT)
# creating object of ShowPdf from tkPDFViewer.
pdf_show = pdf.ShowPdf()
# Adding pdf location and width and height.
V_pdf = pdf_show.pdf_view(master=frame_r,
pdf_location=(doc_list[doc_counter]),
width=90, height=100)
V_pdf.pack(side=tk.LEFT)
#button skip
def skip_callback():
global doc_counter
doc_counter = doc_counter +1
# Here I want to display the next PDF!!
V_pdf = pdf_show.pdf_view(master=frame_r,
pdf_location=(doc_list[doc_counter]),
width=90, height=100)
V_pdf.pack()
print(doc_counter)
button_skip = Button(root, text='skip', command= skip_callback)
button_skip.pack(fill=tk.X, pady=0)
root.mainloop()
When I hit the 'skip' button, the next pdf from the 'parent_path' is supposed to appear where the initial one was displayed.
Thanks for your help!
Flo
I have looked into tkPDFViewer's code and the ShowPdf class has not been designed to load a new document after the initial one.
The reason why the second file is displayed next to the first one is that pdf_show.pdf_view() creates a new Frame which is then packed on the right of the previous one. So you need to destroy the old one first with
pdf_show.frame.destroy()
Then, I have noticed that instead of displaying the second document when clicking on the skip button, what is displayed is a concatenation of both documents. This can be solved by clearing the image list of pdf_show
pdf_show.img_object_li.clear()
Here is the full code:
import tkinter as tk
import glob
from tkPDFViewer import tkPDFViewer as pdf
parent_path = '/home/juliette/Documents'
doc_list = glob.glob((parent_path + "*//*.pdf"))
doc_counter = 0
root = tk.Tk()
root.title('Training Data Creator')
root.geometry("1000x1000")
frame_r = tk.Frame(root, relief=tk.RAISED, borderwidth=1)
frame_r.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)
# creating object of ShowPdf from tkPDFViewer.
pdf_show = pdf.ShowPdf()
# Adding pdf location and width and height.
V_pdf = pdf_show.pdf_view(master=frame_r,
pdf_location=(doc_list[doc_counter]),
width=90, height=100)
V_pdf.pack(side=tk.LEFT)
#button skip
def skip_callback():
global doc_counter
doc_counter = doc_counter +1
# Reset view:
pdf_show.frame.destroy()
pdf_show.img_object_li.clear()
# Display new pdf
V_pdf = pdf_show.pdf_view(master=frame_r,
pdf_location=(doc_list[doc_counter]),
width=90, height=100)
V_pdf.pack(side=tk.LEFT)
print(doc_counter)
button_skip = tk.Button(root, text='skip', command= skip_callback)
button_skip.pack(fill=tk.X, pady=0)
root.mainloop()
By the way, you are importing tkinter twice:
from tkinter import *
import tkinter as tk
so I advise you to choose one of the ways to import it and stick with it, rather than mixing both. Furthermore, I suggest you to use import tkinter as tk, this way you will not crowd the namespace with a large amount of unknown names that can lead to naming conflicts (e.g. if you import Image from PIL and then do from tkinter import *, Image will be the tkinter class, not the PIL one).
I want to show images in the Listbox tkinter widget. My code looks like this:
mylist = Listbox(self.labelFrame3, yscrollcommand=self.yscrollbar.set, selectmode=SINGLE)
mylist.grid(row=0, column=0, rowspan=21, columnspan=21, sticky=(N, S, E, W))
img = Image.open('modeldata/photosamples/amanullahattamuhammad2.0.jpg') # PIL solution
img = img.resize((200, 200), Image.ANTIALIAS) # The (250, 250) is (height, width)
img = ImageTk.PhotoImage(img)
How to print this image in Listbox?
As someone mentioned in a comment you can't put images in a Listbox, but as I mentioned in another, you could use a Text widget instead because you can put images into them. Below is a relatively simple demonstration of how something like that can be done. Its two buttons illustrate how it can display just text or a combination of the two simultaneously. Text widget are actually very versatile.
Anyway, since presumably you want a scrollable list, the Text subclass named tkinter.scrolledtext is used instead of plain one to save work. Since it's a subclass, it can do anything its baseclass can do.
from pathlib import Path
from PIL import Image, ImageTk
import tkinter as tk
from tkinter.constants import *
from tkinter.scrolledtext import ScrolledText
class App:
def __init__(self, image_folder_path, image_file_extensions):
self.root = tk.Tk()
self.image_folder_path = image_folder_path
self.image_file_extensions = image_file_extensions
self.create_widgets()
self.root.mainloop()
def create_widgets(self):
self.list_btn = tk.Button(self.root, text='List Images', command=self.list_images)
self.list_btn.grid(row=0, column=0)
self.show_btn = tk.Button(self.root, text='Show Images', command=self.show_images)
self.show_btn.grid(row=1, column=0)
self.text = ScrolledText(self.root, wrap=WORD)
self.text.grid(row=2, column=0, padx=10, pady=10)
self.text.image_filenames = []
self.text.images = []
def list_images(self):
''' Create and display a list of the images the in folder that have one
of the specified extensions. '''
self.text.image_filenames.clear()
for filepath in Path(self.image_folder_path).iterdir():
if filepath.suffix in self.image_file_extensions:
self.text.insert(INSERT, filepath.name+'\n')
self.text.image_filenames.append(filepath)
def show_images(self):
''' Show the listed image names along with the images themselves. '''
self.text.delete('1.0', END) # Clear current contents.
self.text.images.clear()
# Display images in Text widget.
for image_file_path in self.text.image_filenames:
img = Image.open(image_file_path).resize((64, 64), Image.ANTIALIAS)
img = ImageTk.PhotoImage(img)
self.text.insert(INSERT, image_file_path.name+'\n')
self.text.image_create(INSERT, padx=5, pady=5, image=img)
self.text.images.append(img) # Keep a reference.
self.text.insert(INSERT, '\n')
image_folder_path = 'modeldata/photosamples'
image_file_extensions = {'.jpg', '.png'}
App(image_folder_path, image_file_extensions)
Here's it running on a folder on my computer with the images shown:
I am trying to bind a photo to the list box, but the photo does not appear.
I tried to take a specific photo path here. with the same code above (in the choosePhoto) and it worked. For some reason when in the code inside the function and is binding the function to the listBox, the photo does not appear.
My code:
from tkinter import *
from PIL import ImageTk, Image
from os import *
def openPath(path,listBox):
try:
path2=str(path)
list1= listdir(path2)
listBox.delete(0,END)
for i in range(len(list1)):
listBox.insert(i,list1[i])
except:
print("file does not exist")
def choosePhoto(event):
path=str(textFolder.get())+"\\"+str(listBoxPath.get(ACTIVE))
image1=ImageTk.PhotoImage(Image.open(path))
lbl.configure(image=image1)
print(path)
root = Tk()
root.geometry("450x600")
root.title("project image proccesor")
frame1=Frame(root,width=250,height=100)
frame1.pack(side=LEFT,fill=BOTH)
frame4=Frame(root,width=250,height=100)
frame4.pack(side=RIGHT,fill=BOTH)
lblFolder=Label(frame1,text="Enter folder path:")
lblFolder.grid(row=0,column=0)
textFolder=Entry(frame1,insertwidth=4)
textFolder.grid(rowspan=1,column=0)
listBoxPath=Listbox(frame1)
listBoxPath.grid(row=2)
bChoose=Button(frame1,text="Choose",command=lambda: openPath(textFolder.get(),listBoxPath)).grid(row=1,column=1)
lbl=Label(frame4, text="waiting for photo")
listBoxPath.bind('<<ListboxSelect>>', choosePhoto)
root.mainloop()
There are 3 issues I can see here in your code.
1st. You need to define image1 as a global because this image is currently a local variable in the function and once the function completes the images is deleted unless you define it in the global namespace.
2nd. Your label that is used for displaying the images has not yet been placed on the screen. You need to use some geometry manager (probably grid()) in this case to display the image.
3rd. You are currently using ACTIVE in your selection on the list box. This will results in you selecting what was active prior to you clicking instead of what you just clicked on.
Change this:
list_box_path.get(ACTIVE)
to this:
list_box_path.get(list_box_path.curselection())
I have cleaned up your code a bit to more closely fit the PEP8 standard and added some minor changes and reduced section of code that were not needed.
import tkinter as tk
from PIL import ImageTk, Image
from os import listdir
def open_path(path):
try:
list1 = listdir(path)
list_box_path.delete(0, "end")
for i in range(len(list1)):
list_box_path.insert(i, list1[i])
except:
print("file does not exist")
def choose_photo(event):
global image1
path = Image.open("{}\\{}".format(text_folder.get(), list_box_path.get(list_box_path.curselection())))
image1 = ImageTk.PhotoImage(path)
lbl.configure(image=image1)
root = tk.Tk()
root.geometry("450x600")
root.title("project image processor")
frame1 = tk.Frame(root, width=250, height=100)
frame4 = tk.Frame(root, width=250, height=100)
lbl_folder = tk.Label(frame1, text="Enter folder path:")
text_folder = tk.Entry(frame1, insertwidth=4)
list_box_path = tk.Listbox(frame1)
b_choose = tk.Button(frame1, text="Choose", command=lambda: open_path(text_folder.get()))
lbl = tk.Label(frame4, text="waiting for photo")
frame1.pack(side="left", fill="both")
frame4.pack(side="right", fill="both")
lbl_folder.grid(row=0, column=0)
text_folder.grid(rowspan=1, column=0)
list_box_path.grid(row=2)
b_choose.grid(row=1, column=1)
lbl.grid(row=0, column=0)
list_box_path.bind('<<ListboxSelect>>', choose_photo)
root.mainloop()
I'm trying to create a program using Tkinter that displays a thumbnail from several different directories in on window. So far I have this:
import Tkinter as tk
from PIL import Image, ImageTk
import Image, os
root = tk.Tk()
root.title('Shot Viewer')
w, h, x, y = 1000, 1000, 0, 0
root.geometry("%dx%d+%d+%d" % (w, h, x, y))
#quit
def quit(root):
root.quit()
root.destroy()
path = "/media/Expansion Drive/Heros Mission 3/Scenes/Scene 1-3/Shots/"
labels = []
for files in os.listdir(path):
number = files.split("_")[1]
filed = "/media/Expansion Drive/Heros Mission 3/Scenes/Scene 1-3/Shots/Shot_{} /Frames/Shot_{}_000000.png".format(number, number)
if os.path.lexists(filed) == 'False':
pass
else:
im = Image.open(imageFile)
im.thumbnail((96, 170), Image.ANTIALIAS)
image = ImageTk.PhotoImage(im)
label = tk.Label(root, image=image, name=number)
labels.append(label)
print labels
for label in labels:
panel = label.grid()
panel2.grid(row=2, column=1)
button2 = tk.Button(panel2, text='Quit', command=lambda root=root:quit(root))
button2.grid(row=1, column=1, sticky='NW')
root.mainloop()
However this is not working, does anyone have any suggestions?
Thanks
Tom
Use the glob module to help find the relevant files.
As for images failing to appear:
import Tkinter as tk
from PIL import Image, ImageTk
import glob
root = tk.Tk()
labels = []
for jpeg in glob.glob("C:/Users/Public/Pictures/Sample Pictures/*.jpg")[:5]:
im = Image.open(jpeg)
im.thumbnail((96, 170), Image.ANTIALIAS)
photo = ImageTk.PhotoImage(im)
label = tk.Label(root, image=photo)
label.pack()
label.img = photo # *
# * Each time thru the loop, the name 'photo' has a different
# photoimage assigned to it.
# This means that you need to create a separate, 'longer-lived'
# reference to each photoimage in order to prevent it from
# being garbage collected.
# Note that simply passing a photoimage to a Tkinter widget
# is not enough to keep that photoimage alive.
labels.append(label)
root.mainloop()
I do not think you are handling it correctly where you say panels = label.grid(). Instead, try to just do label.grid so it is not an assignment operator but an action.