Compilation of Pysintaller with ImageTK vs. tk.PhotoImage failed

Question:

In the meantime I have read a lot of articles about the topic Pysintaller and the embedding of images in an exe. The function resource_path was for most the solution for the problem. Furthermore there is the possibility to adjust the .spec file with a.datas +=[(.....)] and apply pyinstaller .spec again. I got my previous information from this page.
My problem with this, unfortunately, is that applying the resource_path function works wonderfully on Windows, but doesn’t help me on Linux. Unfortunately, the image I want to include does not appear here. I also do not get an error message. Now I have adjusted my .spec file as described in the article. If I apply Pysintaller .spec, then I get the message "completed successfully", however the exe cannot be opened any more.
Now I have two suspicions. Either I have configured the spec incorrectly or there are problems loading the image.

import os, sys
import tkinter as tk
from PIL import Image, ImageTk

class pseudo_example:

    def __init__(self):
        self.root = tk.Tk()
        self.root.geometry("600x300")
        self.root.title("About-Dialog")
        self.root.minsize(480, 210)
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_columnconfigure(0, weight=1)

    def resource_path(self,relative_path):
        """ Get absolute path to resource, works for dev and for PyInstaller """
        try:
            # PyInstaller creates a temp folder and stores path in _MEIPASS
            base_path = sys._MEIPASS
        except Exception:
            base_path = os.path.abspath(".")

        return os.path.join(base_path, relative_path)

    def About(self):

        # variable for the grid position
        grid_position = 1
        try:
            photo = tk.Text(self.root, height=10, width=50)
            path = self.resource_path("my_image.png")
            # I suspect that the following part is the source of the error

            self.image = Image.open(path)
            # Resize the image in the given (width, height)
            self.img = self.image.resize((405, 152))
            # Conver the image in TkImage
            self.my_img = ImageTk.PhotoImage(self.img)
            # the following would be the alternative 
            # self.my_img = tk.PhotoImage(file=path)
            # self.my_img = tk.PhotoImage(file=path)
            # self.my_img =  self.my_img.zoom(10)  
            # self.my_img =  self.my_img.subsample(20)

            photo.insert("end", 'n')
            photo.image_create("end", image=self.my_img)
            photo.config(state="disable")
            photo.grid(column=1, row=0, sticky="w")
        except FileNotFoundError:
            grid_position = 0
            self.root.minsize(240, 200)

        about = "About-Dialog"
        about_out = tk.Text(self.root, height=10, width=30)
        about_out.grid(column=0, row=0, sticky="w")
        about_out.insert("end", about)
        about_out.config(state="disable")

        ok_bu = tk.Button(self.root, text="okay", command=lambda: self.root.destroy())
        ok_bu.grid(column=0, row=1, columnspan=grid_position + 1, sticky="nswe")

        self.root.mainloop()

if __name__ == '__main__':
    try:
        gui = pseudo_example()
        gui.About()
    except KeyboardInterrupt:
        print("Interrupted")
        sys.exit()

my .spec:

block_cipher = None


a = Analysis(['my_file.py'],
             pathex=['my_path'],
             binaries=[],
             datas=[('my_images.png', 'img')],
             hiddenimports=[],
             hookspath=[],
             hooksconfig={},
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)


a.datas += [('my_image.png', 'my_path/my_image.png', 'DATA')]

pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)

exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,  
          [],
          name='my_file',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True,
          disable_windowed_traceback=False,
          target_arch=None,
          codesign_identity=None,
          entitlements_file=None,
      image = 'my_path/my_image.png' )

EDIT:
Now I have noticed the following phenomena. If I include an image via tk.PhotoImage(file=path) and use Pyinstaller, then I cannot run the exe. However, if I then adjust my .spec as described above, then the exe works beautifully.
But now I have the problem, that I like to use

self.image = Image.open(path)
self.img = self.image.resize((405, 152))
self.my_img = ImageTk.PhotoImage(self.img)

because I want to resize the image. If I enter pyinstaller --onefile --add-data "my_image.png:img" my_file.py, then I can run the exe, but the image does not appear. So as described above, when I customize .spec, the app no longer opens.
Consequently, I conclude that to use ImageTk.PhotoImage(self.img) I still need some imports or do some special customization. Something similar was described here, but unfortunately it doesn’t help me.

The whole problem could be worked around by using tk.PhotoImage(file=path), however I actually don’t want to edit the .spec file

Asked By: lu3si

||

Answers:

I finally found a solution. The problem why I couldn’t run the app was that the path to the images for the tk.PhotoImage() case wasn’t specified correctly. I simply added base_path=os.path.join(sys._MEIPASS, "img") to the resource_path function and now the embedding works for Linux and Windows.
Problem was that Pyinstaller saved the image in an extra folder, so I had to include the new folder in the path.
Here again the code to the function.

 def resource_path(self,relative_path):
        """ Get absolute path to resource, works for dev and for PyInstaller """
        try:
            # PyInstaller creates a temp folder and stores path in _MEIPASS
            base_path = os.path.join(sys._MEIPASS, "img")
        except Exception:
            base_path = os.path.abspath(".")

        return os.path.join(base_path, relative_path)

A lot of effort for a simple solution 😀

Answered By: lu3si
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.