Posts: 37
Threads: 12
Joined: Jul 2021
I am trying to make a Tkinter GUI with PyMuPDF. however I am geting the error Error: Traceback (most recent call last):
File "C:\Users\INDIAN\Desktop\python exercises\pygametooth\pdf2jpg2.py", line 21, in <module>
image_ext = img['str']
TypeError: tuple indices must be integers or slices, not str
.
can some one help fix this. import fitz
import tempfile
import os
import tkinter as tk
from PIL import Image, ImageTk
# create a temporary directory to store extracted images
temp_dir = tempfile.TemporaryDirectory()
# open PDF file
doc = fitz.open('G:\\Patient OPG\\Arshita Nagpal\\CBCT 48,38\\Axials.pdf')
# iterate over pages
for page_index in range(doc.page_count):
# get the page
page = doc[page_index]
# iterate over images
for image_index, img in enumerate(page.get_images()):
# get the image extension
image_ext = img['str']
# get the pixmap
pixmap = page.get_pixmap(image_index)
# get the image data
image_data = pixmap.tobytes()
# save the image to the temporary directory
image_path = os.path.join(temp_dir.name, f"image{page_index+1}_{image_index}.{image_ext}")
pixmap.save(image_path)
## with open(image_path, 'wb') as f:
## f.write(image_data)
# create a tkinter window
root = tk.Tk()
# iterate over extracted images and display them in the tkinter window
for image_file in os.listdir(temp_dir.name):
image_path = os.path.join(temp_dir.name, image_file)
# open image with PIL
image = Image.open(image_path)
# convert image to tkinter-compatible format
photo = ImageTk.PhotoImage(image)
# create a label with the image and add it to the tkinter window
label = tk.Label(root, image=photo)
label.pack()
# start the tkinter mainloop
root.mainloop()
# delete the temporary directory
temp_dir.cleanup()
Posts: 1,144
Threads: 114
Joined: Sep 2019
What do you get when you print this?
image_ext = img['str']
Posts: 6,779
Threads: 20
Joined: Feb 2020
Post entire error including traceback.
What do you think this does?
image_ext = img['str'] This looks like indexing for a dictionary, but from all the examples I can find, img uses integer indexing, not key indexing.
From the documentation:
https://pymupdf.readthedocs.io/en/latest...mages.html
Quote:The question remains: “How do I know those ‘xref’ numbers of images?”. There are two answers to this:
“Inspect the page objects:” Loop through the items of Page.get_images(). It is a list of list, and its items look like [xref, smask, …], containing the xref of an image. This xref can then be used with one of the above
Posts: 37
Threads: 12
Joined: Jul 2021
Yes 'img' is a dictionary object. So why am I getting a tuple error. I went through the documentation but am unable to completely understand it so as to implement it in my code. using the documentation I am able to extract images from the pdf file and save them in the parent directory on my hard drive. The problem arises when I am trying to create a temporary folder within the program to store the images and access them to be displayed in tkinter window.
(Feb-22-2023, 10:15 PM)deanhystad Wrote: Post entire error including traceback.
What do you think this does?
image_ext = img['str'] This looks like indexing for a dictionary, but from all the examples I can find, img uses integer indexing, not key indexing.
From the documentation:
https://pymupdf.readthedocs.io/en/latest...mages.html
Quote:The question remains: “How do I know those ‘xref’ numbers of images?”. There are two answers to this:
“Inspect the page objects:” Loop through the items of Page.get_images(). It is a list of list, and its items look like [xref, smask, …], containing the xref of an image. This xref can then be used with one of the above
Posts: 6,779
Threads: 20
Joined: Feb 2020
Feb-24-2023, 02:40 PM
(This post was last modified: Feb-27-2023, 02:30 PM by deanhystad.)
The documentation says get_images() returns a list of lists (or tuples), not a list of dictionaries. If you print(img) you will see it is a tuple, not a dictionary.
import fitz
for page in fitz.open('test.pdf'):
for img in page.get_images():
print(type(img), img) Output: <class 'tuple'> (15, 0, 2448, 2448, 8, 'ICCBased', '', 'Im1', 'DCTDecode')
<class 'tuple'> (29, 0, 3264, 2448, 8, 'ICCBased', '', 'Im2', 'DCTDecode')
<class 'tuple'> (35, 0, 3264, 2448, 8, 'ICCBased', '', 'Im5', 'DCTDecode')
<class 'tuple'> (39, 0, 2448, 2448, 8, 'ICCBased', '', 'Im7', 'DCTDecode')
This agrees with the documentation. Page.get_images() returns a list of tuples.
Maybe you skipped a step. To get the image you need to extract the image using the xref. The xref is the first element in the tuples returned by get_images.
import fitz
doc = fitz.open('test.pdf'):
for page in doc:
for xref, *_in page.get_images():
image = doc.extract_image(xref) # Extract the image
print(image.keys()) Output: dict_keys(['ext', 'smask', 'width', 'height', 'colorspace', 'bpc', 'xres', 'yres', 'cs-name', 'image'])
dict_keys(['ext', 'smask', 'width', 'height', 'colorspace', 'bpc', 'xres', 'yres', 'cs-name', 'image'])
dict_keys(['ext', 'smask', 'width', 'height', 'colorspace', 'bpc', 'xres', 'yres', 'cs-name', 'image'])
dict_keys(['ext', 'smask', 'width', 'height', 'colorspace', 'bpc', 'xres', 'yres', 'cs-name', 'image'])
Now we have a dictionary, but there is still not a key named 'str'. However, there is a key named 'ext'.
import fitz
doc = fitz.open('test.pdf'):
for page in doc:
for xref, *_in page.get_images():
print(doc.extract_image(xref)['ext']) Output: jpeg
jpeg
jpeg
jpeg
My images are jpegs.
Posts: 6,779
Threads: 20
Joined: Feb 2020
Feb-26-2023, 02:12 PM
(This post was last modified: Feb-26-2023, 02:12 PM by deanhystad.)
For fun I combined this thread with your other thread about a tkinter app that displays images:
https://python-forum.io/thread-39420.html
This is a tkinter app that displays images extracted from a pdf file. And for some bizarre reason they start spinning when clicked.
import math
import statistics
import fitz
import numpy as np
import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
from io import BytesIO
IMAGE_SIZE = (100, 100) # Resize all images to this size
LABEL_SIZE = 140 # Max size of rotated image
class SpinningImageButton(tk.Frame):
"""A Button that has an image you can rotate"""
def __init__(self, parent, image, *args, frames=20, command=None, **kwargs):
super().__init__(parent, *args, **kwargs)
self.running = False
self.index = 0
self.command = command
# Guess a fill color using the the top row of pixels
pixels = [tuple(p) for p in np.array(image)[0]]
rgb = statistics.mode(pixels)[:3]
# Create movie frames
image.thumbnail(IMAGE_SIZE)
angle = 360 / frames
self.images = [
ImageTk.PhotoImage(image.rotate(i*angle, fillcolor=rgb, expand=True))
for i in range(frames)
]
# Make a label to display the image.
self.image = tk.Label(
self,
image=self.images[0],
width=LABEL_SIZE,
height=LABEL_SIZE,
bg=f'#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}')
self.image.pack(anchor=tk.CENTER)
self.image.bind('<Button-1>', func=self._click)
def _click(self, *args):
"""Called when mouse clicks on me"""
if self.command:
self.command(self)
def _next(self, wait):
"""Display successive images to make it appear the image is spinning"""
if self.running:
self.index = (self.index + 1) % len(self.images)
self.image.config(image=self.images[self.index])
self.after(wait, self._next, wait)
def start(self, period=2):
"""Start spinning the image"""
self.running = True
self._next(int(period * 1000 / len(self.images)))
def stop(self):
"""Stop spinning the image"""
self.running = False
class MyWindow(tk.Tk):
"""Window to show off my fancy spinning buttons"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make a menubar instead of a bunch of buttons
menubar = tk.Menu(self)
menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="File", menu=menu)
menu.add_command(label="Open PDF", command=self.open_pdf)
menu.add_separator()
menu.add_command(label="Quit", command=self.destroy)
self.config(menu=menubar)
self.images = []
self.frame = tk.Frame(self, width=300, height=300)
self.frame.pack(expand=True, fill=tk.BOTH)
def open_pdf(self):
"""Populate window with images extracted from pdf file"""
pdf = filedialog.askopenfilename()
if pdf:
# Replace old image buttons with new
for image in self.images:
image.grid_forget()
self.images = []
try:
doc = fitz.open(pdf)
for page in doc:
for xref, *_ in page.get_images():
image_bytes = doc.extract_image(xref)["image"]
image = Image.open(BytesIO(image_bytes))
self.images.append(
SpinningImageButton(
self.frame,
image,
frames=60,
command=self.toggle
)
)
except Exception as msg:
messagebox.showerror('Error', str(msg))
# Organize the images on a grid.
cols = int(math.ceil(len(self.images)**0.5))
for i, image in enumerate(self.images):
image.grid(row=i // cols, column=i % cols)
def toggle(self, image):
"""Toggle image spinning on/off when button clicked"""
if image.running:
image.stop()
else:
image.start()
MyWindow().mainloop()
Posts: 37
Threads: 12
Joined: Jul 2021
Thanks deanhystad,
I got this sample code and modified it . It works fine extracts the images and saves them in a temp folder and then displays them in the tkinter window. import fitz # PyMuPDF
import io
import os
import tempfile
from PIL import Image
import tkinter as tk
from tkinter import ttk
from PIL import ImageTk
# file path you want to extract images from
file = 'G:\\Patient OPG\\Arshita Nagpal\\CBCT 48,38\\Axials.pdf'
# open the file
pdf_file = fitz.open(file)
# create temporary folder to save images
with tempfile.TemporaryDirectory() as temp_folder:
# iterate over PDF pages
for page_index in range(len(pdf_file)):
# get the page itself
page = pdf_file[page_index]
# get image list
image_list = page.get_images()
# printing number of images found in this page
if image_list:
print(f"[+] Found a total of {len(image_list)} images in page {page_index}")
else:
print("[!] No images found on page", page_index)
for image_index, img in enumerate(image_list, start=1):
# get the XREF of the image
xref = img[0]
# extract the image bytes
base_image = pdf_file.extract_image(xref)
image_bytes = base_image["image"]
# get the image extension
image_ext = base_image["ext"]
# load it to PIL
image = Image.open(io.BytesIO(image_bytes))
# save it to temporary folder
image.save(os.path.join(temp_folder, f"image{page_index+1}_{image_index}.{image_ext}"))
print(f"[+] All images saved to {temp_folder}")
# create a window using Tkinter
root = tk.Tk()
root.title("Extracted Images")
# create a label to display the images
image_label = ttk.Label(root)
image_label.pack()
# iterate over the image files in the temporary folder and display them
image_files = sorted(os.listdir(temp_folder))
global current_index
current_index = 0
pil_image = Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
image_label.configure(image=tk_image)
def on_key_press(event):
global current_index
if event.keysym == "Right":
current_index = (current_index + 1) % len(image_files)
pil_image = Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
image_label.configure(image=tk_image)
image_label.image = tk_image
elif event.keysym == "Left":
current_index = (current_index - 1) % len(image_files)
pil_image = Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
image_label.configure(image=tk_image)
image_label.image = tk_image
# bind the left and right arrow keys to change the displayed image
root.bind("<Left>", on_key_press)
root.bind("<Right>", on_key_press)
# start the Tkinter event loop
root.mainloop() (Feb-26-2023, 02:12 PM)deanhystad Wrote: For fun I combined this thread with your other thread about a tkinter app that displays images:
https://python-forum.io/thread-39420.html
This is a tkinter app that displays images extracted from a pdf file. And for some bizarre reason they start spinning when clicked.
import math
import statistics
import fitz
import numpy as np
import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
from io import BytesIO
IMAGE_SIZE = (100, 100) # Resize all images to this size
LABEL_SIZE = 140 # Max size of rotated image
class SpinningImageButton(tk.Frame):
"""A Button that has an image you can rotate"""
def __init__(self, parent, image, *args, frames=20, command=None, **kwargs):
super().__init__(parent, *args, **kwargs)
self.running = False
self.index = 0
self.command = command
# Guess a fill color using the the top row of pixels
pixels = [tuple(p) for p in np.array(image)[0]]
rgb = statistics.mode(pixels)[:3]
# Create movie frames
image.thumbnail(IMAGE_SIZE)
angle = 360 / frames
self.images = [
ImageTk.PhotoImage(image.rotate(i*angle, fillcolor=rgb, expand=True))
for i in range(frames)
]
# Make a label to display the image.
self.image = tk.Label(
self,
image=self.images[0],
width=LABEL_SIZE,
height=LABEL_SIZE,
bg=f'#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}')
self.image.pack(anchor=tk.CENTER)
self.image.bind('<Button-1>', func=self._click)
def _click(self, *args):
"""Called when mouse clicks on me"""
if self.command:
self.command(self)
def _next(self, wait):
"""Display successive images to make it appear the image is spinning"""
if self.running:
self.index = (self.index + 1) % len(self.images)
self.image.config(image=self.images[self.index])
self.after(wait, self._next, wait)
def start(self, period=2):
"""Start spinning the image"""
self.running = True
self._next(int(period * 1000 / len(self.images)))
def stop(self):
"""Stop spinning the image"""
self.running = False
class MyWindow(tk.Tk):
"""Window to show off my fancy spinning buttons"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make a menubar instead of a bunch of buttons
menubar = tk.Menu(self)
menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="File", menu=menu)
menu.add_command(label="Open PDF", command=self.open_pdf)
menu.add_separator()
menu.add_command(label="Quit", command=self.destroy)
self.config(menu=menubar)
self.images = []
self.frame = tk.Frame(self, width=300, height=300)
self.frame.pack(expand=True, fill=tk.BOTH)
def open_pdf(self):
"""Populate window with images extracted from pdf file"""
pdf = filedialog.askopenfilename()
if pdf:
# Replace old image buttons with new
for image in self.images:
image.grid_forget()
self.images = []
try:
doc = fitz.open(pdf)
for page in doc:
for xref, *_ in page.get_images():
image_bytes = doc.extract_image(xref)["image"]
image = Image.open(BytesIO(image_bytes))
self.images.append(
SpinningImageButton(
self.frame,
image,
frames=60,
command=self.toggle
)
)
except Exception as msg:
messagebox.showerror('Error', str(msg))
# Organize the images on a grid.
cols = int(math.ceil(len(self.images)**0.5))
for i, image in enumerate(self.images):
image.grid(row=i // cols, column=i % cols)
def toggle(self, image):
"""Toggle image spinning on/off when button clicked"""
if image.running:
image.stop()
else:
image.start()
MyWindow().mainloop()
How ever when I try to add a select button the select button is seen in the GUI , allows you to select a pdf file and displays only the first image and when arrow keys are pressed I get the error message.. here I am posting the second code and the error message . Can some one help solve this. import fitz # PyMuPDF
import io
import os
import tempfile
import PIL.Image
from PIL import ImageTk
import tkinter as tk
from tkinter import filedialog
from tkinter import*
root=Tk()
# function to extract images from the PDF file
def extract_images(file_path):
# open the file
pdf_file = fitz.open(file_path)
# create temporary folder to save images
with tempfile.TemporaryDirectory() as temp_folder:
# iterate over PDF pages
for page_index in range(len(pdf_file)):
# get the page itself
page = pdf_file[page_index]
# get image list
image_list = page.get_images()
# printing number of images found in this page
if image_list:
print(f"[+] Found a total of {len(image_list)} images in page {page_index}")
else:
print("[!] No images found on page", page_index)
for image_index, img in enumerate(image_list, start=1):
# get the XREF of the image
xref = img[0]
# extract the image bytes
base_image = pdf_file.extract_image(xref)
image_bytes = base_image["image"]
# get the image extension
image_ext = base_image["ext"]
# load it to PIL
image = Image.open(io.BytesIO(image_bytes))
# save it to temporary folder
image.save(os.path.join(temp_folder, f"image{page_index+1}_{image_index}.{image_ext}"))
print(f"[+] All images saved to {temp_folder}")
# call the function to display images after a delay of 500ms
root.after(500, display_images, temp_folder)
# function to display the images
def display_images(temp_folder):
# iterate over the image files in the temporary folder and display them
image_files = sorted(os.listdir(temp_folder))
global current_index
current_index = 0
# create a canvas to display the images
canvas = tk.Canvas(root, width=600, height=600)
canvas.pack()
# display the first image
pil_image = Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
canvas.create_image(0, 0, anchor="nw", image=tk_image)
def on_key_press(event):
global current_index
if event.keysym == "Right":
current_index = (current_index + 1) % len(image_files)
pil_image = Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
canvas.create_image(0, 0, anchor="nw", image=tk_image)
elif event.keysym == "Left":
current_index = (current_index - 1) % len(image_files)
pil_image = Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
canvas.create_image(0, 0, anchor="nw", image=tk_image)
# bind the canvas to the key press event
canvas.bind("<KeyPress>", on_key_press)
# set the focus on the canvas so that it can receive the key press events
canvas.focus_set()
# function to get the path of the PDF file
def select_pdf_file():
# open file dialog to choose a PDF file
file_path = filedialog.askopenfilename(filetypes=[("PDF Files", "*.pdf")])
if file_path:
# open the file
pdf_file = fitz.open(file_path)
# create temporary folder to save images
with tempfile.TemporaryDirectory() as temp_folder:
# iterate over PDF pages
for page_index in range(len(pdf_file)):
# get the page itself
page = pdf_file[page_index]
# get image list
image_list = page.get_images()
# printing number of images found in this page
if image_list:
print(f"[+] Found a total of {len(image_list)} images in page {page_index}")
else:
print("[!] No images found on page", page_index)
for image_index, img in enumerate(image_list, start=1):
# get the XREF of the image
xref = img[0]
# extract the image bytes
base_image = pdf_file.extract_image(xref)
image_bytes = base_image["image"]
# get the image extension
image_ext = base_image["ext"]
# load it to PIL
image = PIL.Image.open(io.BytesIO(image_bytes))
# save it to temporary folder
image.save(os.path.join(temp_folder, f"image{page_index+1}_{image_index}.{image_ext}"))
print(f"[+] All images saved to {temp_folder}")
# iterate over the image files in the temporary folder and display them
image_files = sorted(os.listdir(temp_folder))
global current_index
current_index = 0
show_image(temp_folder, image_files[current_index])
def on_key_press(event):
global current_index
if event.keysym == "Right":
current_index = (current_index + 1) % len(image_files)
pil_image = PIL.Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
image_label.config(image=tk_image)
elif event.keysym == "Left":
current_index = (current_index - 1) % len(image_files)
pil_image = PIL.Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
image_label.config(image=tk_image)
root.bind("<Key>", on_key_press)
def show_image(folder, file_name):
pil_image = PIL.Image.open(os.path.join(folder, file_name))
tk_image = ImageTk.PhotoImage(pil_image)
print(tk_image)
image_label = tk.Label(root)
image_label.pack()
image_label.config(image=tk_image)
image_label.image = tk_image
# create button to select a PDF file
select_file_button = tk.Button(root, text="Select PDF File", command=select_pdf_file)
select_file_button.pack()
root.mainloop() Error: [+] Found a total of 21 images in page 0
[+] All images saved to C:\Users\INDIAN\AppData\Local\Temp\tmpi1fk3lxi
pyimage1
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\INDIAN\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
File "C:\Users\INDIAN\Desktop\python exercises\pygametooth\pdf2jpgtemporaryfoldertkinter3.py", line 124, in on_key_press
pil_image = PIL.Image.open(os.path.join(temp_folder, image_files[current_index]))
File "C:\Users\INDIAN\AppData\Local\Programs\Python\Python38\lib\site-packages\PIL\Image.py", line 3092, in open
fp = builtins.open(filename, "rb")
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\INDIAN\\AppData\\Local\\Temp\\tmpi1fk3lxi\\image1_10.png'
>>>
Posts: 6,779
Threads: 20
Joined: Feb 2020
Feb-28-2023, 05:04 PM
(This post was last modified: Feb-28-2023, 07:51 PM by deanhystad.)
https://docs.python.org/3/library/tempfile.html
Quote:class tempfile.TemporaryDirectory(suffix=None, prefix=None, dir=None, ignore_cleanup_errors=False)
This class securely creates a temporary directory using the same rules as mkdtemp(). The resulting object can be used as a context manager (see Examples). On completion of the context or destruction of the temporary directory object, the newly created temporary directory and all its contents are removed from the filesystem.
In other words, as soon as you leave the context created by this:
with tempfile.TemporaryDirectory() as temp_folder: The temporary directory is deleted.
There is no need to write the image data to files. Create the images and keep them in a list. If you want to write files to a temporary directory, you could wrap you program in the temp_folder context.
with tempfile.TemporaryDirectory() as temp_folder:
window = function_that_creates_your_main_window()
window.mainloop() Or you could make temp_folder a global variable
def select_pdf_file():
global temp_folder, current_index
file_path = filedialog.askopenfilename(filetypes=[("PDF Files", "*.pdf")])
if file_path:
pdf_file = fitz.open(file_path)
current_index = 0
temp_folder = tempfile.TemporaryDirectory()
image_index = 0
for page in pdf_file:
for xref, *_ in page.get_images():
# extract the image
info = pdf_file.extract_image(xref)
image = PIL.Image.open(io.BytesIO(info ["image"])) #<- Why not put this in a list??
image_file = os.path.join(temp_folder.name, f"image{image_index}.{info ["ext"]}")
image.save(os.path.join(temp_folder.name, image_file))
show_image(temp_folder.name, image_file)
image_index += 1 Making temp_folder a global variable means the temporary folder stays alive until you reassign the temp_folder variable (no reference->delete folder), exit the program, or call the cleanup() function (temp_folder.cleanup()).
One last thing, this does not belong inside select_pdf_file()
def on_key_press(event):
global current_index
if event.keysym == "Right":
current_index = (current_index + 1) % len(image_files)
pil_image = PIL.Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
image_label.config(image=tk_image)
elif event.keysym == "Left":
current_index = (current_index - 1) % len(image_files)
pil_image = PIL.Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
image_label.config(image=tk_image)
root.bind("<Key>", on_key_press) You can embed a function inside another function, but you should only do this with "helper" functions, or if you are making a closure. I don't think either applies in this case.
Posts: 37
Threads: 12
Joined: Jul 2021
Thanks deanhystad . I rewrote the whole script and its working. However I have a few issues.
1) There seems to be lag in loading of the first image.
2) I would like to have the exit button to stop the program and allow for a new file/ folder to be selected .
import fitz # PyMuPDF
import io
import os
import tempfile
import PIL.Image
from PIL import ImageTk
import tkinter as tk
from tkinter import filedialog
from tkinter import *
root = tk.Tk()
root.title("Viewer")
photo=PhotoImage(file="C:\\Users\\INDIAN\Desktop\\python exercises\\Viewer\\Logo Viewer.png")
label = Label(root,image = photo,bg="light blue")
label.image = photo # keep a reference!
label.grid(column=12,row=1,rowspan=2 ,ipadx=5, ipady=5,sticky=tk.W)
root.geometry("1200x800")
root.configure(background='light blue')
tk.Label(root, text="Viewer",font="Algerian 20 bold",bg="light blue").grid(column=12,row=1, ipadx=150, ipady=10,sticky=tk.NE)
canvas = tk.Canvas(root, width=800, height=600,bg='black')
canvas.grid(column=1,row=1,columnspan=10,ipadx=10,ipady=10,sticky=tk.NW)
############################################################################################
# Create a temporary directory
temp_dir = tempfile.TemporaryDirectory()
print(f"[+] All images saved to {temp_dir}")
###############################################################################################
def select_pdf_file():
global images
# open file dialog to choose a PDF file
file_path = filedialog.askopenfilename(filetypes=[("PDF Files", "*.pdf")])
if file_path:
# open the file
pdf_file = fitz.open(file_path)
for page_index in range(len(pdf_file)):
# get the page itself
page = pdf_file[page_index]
# get image list
image_list = page.get_images()
# printing number of images found in this page
if image_list:
print(f"[+] Found a total of {len(image_list)} images in page {page_index}")
else:
print("[!] No images found on page", page_index)
for image_index, img in enumerate(image_list, start=1):
# get the XREF of the image
xref = img[0]
# extract the image bytes
base_image = pdf_file.extract_image(xref)
image_bytes = base_image["image"]
# get the image extension
image_ext = base_image["ext"]
# load it to PIL
image = PIL.Image.open(io.BytesIO(image_bytes))
# save it to temporary folder
image.save(os.path.join(temp_dir.name, f"image{page_index+1}_{image_index}.{image_ext}"))
print(f"[+] All images saved to {temp_dir}")
# iterate over the image files in the temporary folder and display them
image_files = sorted(os.listdir(temp_dir.name))
global current_index
current_index = 0
images = []
for filename in os.listdir(temp_dir.name):
if filename.endswith('.png') or filename.endswith('.bmp') or filename.endswith('.jpg') or filename.endswith('.bmp') or filename.endswith('.dcm'):
images.append(os.path.join(temp_dir.name, filename))
if images:
show_image(0, images)
####################################################################################################
def select_folder():
global images
global folder_path
# Open a file dialog to select the folder
folder_path = filedialog.askdirectory()
# Load the images from the selected folder
images=[]
for filename in os.listdir(folder_path):
if filename.endswith('.png') or filename.endswith('.bmp') or filename.endswith('.jpg') or filename.endswith('.bmp') or filename.endswith('.dcm'):
images.append(os.path.join(folder_path,filename))
show_image(0, images)
global current_index
current_index = 0
######################################################################################################
def show_image(index, images):
global current_index
current_index = index
image_path = images[index]
image = PIL.Image.open(image_path)
image = image.resize((800, 600))
photo = ImageTk.PhotoImage(image)
canvas.create_image(0, 0, anchor=tk.NW, image=photo)
canvas.image = photo
current_index = index
######################################################################################################
def on_left_arrow(event):
global current_index
if current_index > 0:
current_index -= 1
show_image(current_index, images)
def on_right_arrow(event):
global current_index
if current_index < len(images) - 1:
current_index += 1
show_image(current_index, images)
def exit_program():
root.destroy()
##########################################################################
# Set the initial image
current_image = 0
## Button & packing it and assigning it a command
tk.Button(text=" Select PDF ", command=select_pdf_file).grid(row=13, column=11)
button1 = tk.Button(root, text="Select Folder", command=select_folder).grid(row=13,column=12)
button2 = tk.Button(root, text="Exit", command=select_folder).grid(row=13,column=12,padx=60,pady=0,sticky=tk.W)
# bind the arrow keys to navigate the images
root.bind('<Left>', on_left_arrow)
root.bind('<Right>',on_right_arrow)
##Button(text=" Select File ",command=select_pdf_file).grid(row=13, column=3)
root.mainloop() (Feb-28-2023, 05:04 PM)deanhystad Wrote: https://docs.python.org/3/library/tempfile.html
Quote:class tempfile.TemporaryDirectory(suffix=None, prefix=None, dir=None, ignore_cleanup_errors=False)
This class securely creates a temporary directory using the same rules as mkdtemp(). The resulting object can be used as a context manager (see Examples). On completion of the context or destruction of the temporary directory object, the newly created temporary directory and all its contents are removed from the filesystem.
In other words, as soon as you leave the context created by this:
with tempfile.TemporaryDirectory() as temp_folder: The temporary directory is deleted.
There is no need to write the image data to files. Create the images and keep them in a list. If you want to write files to a temporary directory, you could wrap you program in the temp_folder context.
with tempfile.TemporaryDirectory() as temp_folder:
window = function_that_creates_your_main_window()
window.mainloop() Or you could make temp_folder a global variable
def select_pdf_file():
global temp_folder, current_index
file_path = filedialog.askopenfilename(filetypes=[("PDF Files", "*.pdf")])
if file_path:
pdf_file = fitz.open(file_path)
current_index = 0
temp_folder = tempfile.TemporaryDirectory()
image_index = 0
for page in pdf_file:
for xref, *_ in page.get_images():
# extract the image
info = pdf_file.extract_image(xref)
image = PIL.Image.open(io.BytesIO(info ["image"])) #<- Why not put this in a list??
image_file = os.path.join(temp_folder.name, f"image{image_index}.{info ["ext"]}")
image.save(os.path.join(temp_folder.name, image_file))
show_image(temp_folder.name, image_file)
image_index += 1 Making temp_folder a global variable means the temporary folder stays alive until you reassign the temp_folder variable (no reference->delete folder), exit the program, or call the cleanup() function (temp_folder.cleanup()).
One last thing, this does not belong inside select_pdf_file()
def on_key_press(event):
global current_index
if event.keysym == "Right":
current_index = (current_index + 1) % len(image_files)
pil_image = PIL.Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
image_label.config(image=tk_image)
elif event.keysym == "Left":
current_index = (current_index - 1) % len(image_files)
pil_image = PIL.Image.open(os.path.join(temp_folder, image_files[current_index]))
tk_image = ImageTk.PhotoImage(pil_image)
image_label.config(image=tk_image)
root.bind("<Key>", on_key_press) You can embed a function inside another function, but you should only do this with "helper" functions, or if you are making a closure. I don't think either applies in this case.
Posts: 6,779
Threads: 20
Joined: Feb 2020
Mar-05-2023, 04:09 AM
(This post was last modified: Mar-05-2023, 04:09 AM by deanhystad.)
Stop using the reply button unless you want to reference something in the previous post. Use the New Reply instead.
How long is the lag? Is the lag when you open the pdf or when you push an arrow key? Not writing image files would speed things significantly. You don't need to make image files.
A few comments:
Your "Select PDF" button doesn't have a parent. This can mess with laying out controls in the window.
This sets button1 = None because grid() returns None.
button1 = tk.Button(root, text="Select Folder", command=select_folder).grid(row=13,column=12) Do not do wildcard imports. It creates tons of variables in your program that you might not know about and makes it hard to track down where functions or variables are imported.
from tkinter import * # Bad! And you don't even use it. If all a function does is call another function, you only need one of the functions. Set command=root.destroy instead of writing this:
def exit_program():
root.destroy() You should use buttons to change the image, not key presses. Binding key press events doesn't give your users any hints about how to use the program. It also forces changing from the mouse to the keyboard. Think about the user experience.
on_left_arrow and on_right_arrow are nearly identical. If the only difference between two function is one function adds 1 and the other subtracts one, you don't need two functions. Use a function argument to add or subtract 1.
The way you use grid is odd. The row and column index number should mean something. Yours' appear to be arbitrary. You have more rows and columns than widgets.
Why are you using grid? This is not really a grid like layout. Where are there rows or columns? I think pack is better than grid for most window layouts.
Avoid setting the application window size. Let the layout manager determine how large the window needs to be. An image viewer program should be resizable.
root.geometry("1200x800") # let the user decide how big the window should be Use fewer comments. Good code it is easy to read. Lots of comments often make code harder to read, and they usually end up being misleading because the code changes but the comments remain the same. Instead of this:
# get the image extension
image_ext = base_image["ext"]
# load it to PIL
image = PIL.Image.open(io.BytesIO(image_bytes))
# save it to temporary folder
image.save(os.path.join(temp_dir.name, f"image{page_index+1}_{image_index}.{image_ext}")) Group your comments to describe the purpose of the following code. Think of them as a narrative or context or anything that cannot be inferred by reading the code. Do not use comments as pseudocode.
# Extract imags into a temporary folder. The temporary is used for image storage.
image_ext = base_image["ext"]
image = PIL.Image.open(io.BytesIO(image_bytes))
image.save(os.path.join(temp_dir.name, f"image{page_index+1}_{image_index}.{image_ext}")) Why do you have an exit button? Doesn't the window manager provide a way to close the window?
That might sound a little harsh, but for the most part I think you wrote a really nice program. I had a lot of fun playing with it and wrote a version that I'll keep around to extract images from PDF files.
I added in a rotate button. It only took until now to realize why you were rotating images in your other post. I use buttons to select images instead of the mystifying key presses. I got rid of the exit button because the window manager provides one. I save the images in a list. This is faster and it lets me rotate an image and have it stay rotated. It is also a lot shorter and clearer code using an integer index to refer an image than using an integer index to reference a filename in a list to read the file and get the image. And I made the program resizable. Resize the window and it resizes the image (up to a point).
import fitz
import io
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
from pathlib import Path
def select_pdf_file():
"""Load images from a PDF file"""
global images
if file := filedialog.askopenfilename(filetypes=[("PDF", "*.pdf")]):
images = []
with fitz.open(file) as doc:
for page in doc:
for xref, *_ in page.get_images():
image = doc.extract_image(xref)
images.append(Image.open(io.BytesIO(image["image"])))
show_image(0)
def select_folder():
"""Load images from a folder"""
global images
if folder := filedialog.askdirectory():
images = []
for file in Path(folder).iterdir():
if file.suffix.lower() in (".png", ".bmp", ".jpg", ".bmp", ".dcm"):
images.append(Image.open(file))
show_image(0)
def show_image(index):
"""Display selected image"""
global img_index
canvas.delete("all")
size = (canvas.winfo_width(), canvas.winfo_height())
if images:
img_index = index % len(images)
image = images[img_index].copy() # Keep original image
image.thumbnail(size)
image = ImageTk.PhotoImage(image)
canvas.create_image(
size[0] / 2, size[1] / 2, anchor=tk.CENTER, image=image
)
canvas.image = image
else:
canvas.create_text(
size[0] / 2, size[1] / 2, anchor=tk.CENTER, text="No Images",
fill="white", font=('Helvetica 15 bold')
)
def rotate_image():
"""Rotate selected image 90 degrees"""
if images:
images[img_index] = images[img_index].rotate(-90, expand=True)
show_image(img_index)
images = []
img_index = 0
root = tk.Tk()
canvas = tk.Canvas(root, bg="black")
canvas.bind("<Configure>", lambda event: show_image(img_index))
canvas.pack(padx=10, pady=10, side=tk.TOP, expand=True, fill=tk.BOTH)
bbar = tk.Frame(root)
bbar.pack(side=tk.TOP, fill=tk.X, padx=10, pady=(0, 10))
button = tk.Button(bbar, text="<<", command=lambda: show_image(img_index-1))
button.pack(side=tk.LEFT)
button = tk.Button(bbar, text="Select PDF", command=select_pdf_file)
button.pack(side=tk.LEFT, expand=True, fill=tk.X)
button = tk.Button(bbar, text="Select Folder", command=select_folder)
button.pack(side=tk.LEFT, expand=True, fill=tk.X)
button = tk.Button(bbar, text="Rotate Image", command=rotate_image)
button.pack(side=tk.LEFT, expand=True, fill=tk.X)
button = tk.Button(bbar, text=">>", command=lambda: show_image(img_index+1))
button.pack(side=tk.LEFT)
root.mainloop()
|