Python Forum
tuple indices must be integers or slices, not str
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
tuple indices must be integers or slices, not str
#1
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()
Reply
#2
What do you get when you print this?
image_ext = img['str']
I welcome all feedback.
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags


Reply
#3
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
Reply
#4
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
Reply
#5
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.
Reply
#6
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()
Reply
#7
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' >>>
Reply
#8
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.
Reply
#9
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.
Reply
#10
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()
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  No matter what I do I get back "List indices must be integers or slices, not list" Radical 4 1,175 Sep-24-2023, 05:03 AM
Last Post: deanhystad
  boto3 - Error - TypeError: string indices must be integers kpatil 7 1,257 Jun-09-2023, 06:56 PM
Last Post: kpatil
  Response.json list indices must be integers or slices, not str [SOLVED] AlphaInc 4 6,419 Mar-24-2023, 08:34 AM
Last Post: fullytotal
  "TypeError: string indices must be integers, not 'str'" while not using any indices bul1t 2 2,038 Feb-11-2023, 07:03 PM
Last Post: deanhystad
  Error "list indices must be integers or slices, not str" dee 2 1,469 Dec-30-2022, 05:38 PM
Last Post: dee
  TypeError: string indices must be integers JonWayn 12 3,400 Aug-31-2022, 03:29 PM
Last Post: deanhystad
  TypeError: list indices must be integers or slices, not range Anldra12 2 2,586 Apr-22-2022, 10:56 AM
Last Post: Anldra12
  string indices must be integers when parsing Json ilknurg 3 6,385 Mar-10-2022, 11:02 AM
Last Post: DeaD_EyE
  code with no tuple gets : IndexError: tuple index out of range Aggam 4 2,834 Nov-04-2020, 11:26 AM
Last Post: Aggam
  TypeError: string indices must be integers hendern 2 3,025 Oct-02-2020, 10:16 PM
Last Post: hendern

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020