valentinitnelav / img-with-box-from-excel

boxcel: Integrate Excel with Python for visualizing images with their corresponding bounding boxes for object detection annotation workflows
BSD 3-Clause "New" or "Revised" License
3 stars 0 forks source link

Convert the tool into an executable file for Windows #16

Open valentinitnelav opened 1 year ago

valentinitnelav commented 1 year ago

Convert the python scripts into a standalone executable to run the tool with the implemented GUI.

For inspiration:

I'll have to execute this on the Windows virtual machine, because if the operation runs under Linux, then it produces an executable for Linux.

valentinitnelav commented 1 year ago

How to use PyInstaller

I used the PyInstaller - see documentation here https://pyinstaller.org/en/stable/usage.html

Here are the steps that I took. On the Windows virtualbox, in the command prompt (~terminal) I did the followings:

# Move to the Documents folder
cd C:\Users\%USERNAME%\Documents

# Make a new directory there:
mkdir boxcel_exe
cd boxcel_exe

# One needs to create a Python environment so that PyInstaller works properly. 
# Idea suggested at https://stackoverflow.com/a/60789588
# Create a Python environment named "env":
python -m venv env
# Activate the environment
env\Scripts\activate.bat

# Install the needed packages so that PyInstaller packages them as well
(env) >pip install pandas
(env) >pip install Pillow
(env) >pip install xlwings
(env) >pip install pyinstaller

# FYI - list all installed dependencies. 
# Some of the installed packages above install their own dependencies too.
(env) >pip list
Package                   Version
------------------------- ---------
altgraph                  0.17.3
future                    0.18.2
numpy                     1.24.0
pandas                    1.5.2
pefile                    2022.5.30
Pillow                    9.3.0
pip                       22.3.1
pyinstaller               5.7.0
pyinstaller-hooks-contrib 2022.14
python-dateutil           2.8.2
pytz                      2022.7
pywin32                   305
pywin32-ctypes            0.2.0
setuptools                65.5.0
six                       1.16.0
xlwings                   0.28.6

# Clone this repository
(env) >git clone https://github.com/valentinitnelav/img-with-box-from-excel

# Make a folder that will contain the output of PyInstaller
(env) >mkdir deploy
(env) >cd deploy

# Make a single exe file (in deploy/dist)
# https://pyinstaller.org/en/stable/operating-mode.html#how-the-one-file-program-works
# Break the long command in multiple lines with the ^ separator (for Windows only):
(env) >pyinstaller ^
--paths "C:\Users\user\Documents\boxcel_exe\img-with-box-from-excel\src\boxcel" ^
--add-data "C:\Users\user\Documents\boxcel_exe\img-with-box-from-excel\src\boxcel\display_images.py;." ^
--onefile ^
--noconsole ^
--name boxcel ^
--debug all ^
"C:\Users\user\Documents\boxcel_exe\img-with-box-from-excel\src\boxcel\gui.py"

# --paths: the path to the folder with the python scripts
# --add-data: here the python script (that exists in --paths), but it is copied & edited at run time as if
# it is a data file.
# --onefile: tells pyinstaller to make a single exe file (saved in ./deploy/dist). The alternative, 
# by not mentioning --onefile, is to create a folder with the exe file + other needed files.
# --noconsole: tells pyinstaller to not display a console at run time when the exe file is run.
# --name: the name of the exe file
# --debug all: can help with more verbose output in the console 
# (the console that appears creating the exe file)

# Deactivate the environment
(env) >deactivate

The final folder structures is:

boxcel_exe/
├─ env/
├─ img-with-box-from-excel/
\─ deploy/
   ├─ build/
   ├─ dist/
   │  \─ boxcel.exe
   \─ boxcel.spec

Where deploy\dist\boxcel.exe is the exe file that will run the application without the need of installing Python. This file can be moved anywhere. It doesn't have to be in its current location.

Encountered errors & debugging

For debugging purposes use the --debug all option with pyinstaller. But do not use --noconsole (this option stops the console to be displayed). This can help with more verbose output in the console. Additionally, when you test the exe file, open a command prompt, navigate to the directory containing the exe and run the exe file by typing its name, then hit Enter:

cd Documents\boxcel_exe\deploy\dist
boxcel.exe

In this way you will benefit of having error messages in a console when debugging.

Encountered errors

Missing module errors

In order to avoid missing module errors, one needs to add all the needed scripts wit the --paths argument to pyinstaller command above. Make sure to give the entire path and use the quotations. Example of error message for missing module:

Traceback (most recent call last):
  File "boxcel\gui.py", line 13, in <module>
ModuleNotFoundError: No module named 'start_project'
[14296] Failed to execute script 'gui' due to unhandled exception!

Here it was also suggested to delete the __init__.py but this is not encouraged - see this.

Missing data or script files

If you get an error message like:

" [Error 2] No such file or directory:
'C:\\Users\\user\\AppData\\Local\\Temp\\_MEI112962\\base_library.zip\\display_images.py' 

It is because you need to include the script display_images.py as data file with the --add-data argument.

Other options & lessons learned

The advantage of producing a single exe is that it makes it easier for the user. However, it can be a bit slower when it runs and if it crashes, it will create a bunch of temp folders like C:\Users\vs66tavy\AppData\Local\Temp\_MEIxxxxxx (where xxxxxx is a random number) - https://pyinstaller.org/en/stable/operating-mode.html#how-the-one-file-program-works

The alternative is to produce a folder that can be distributed to the users by omitting --onefile (see https://pyinstaller.org/en/stable/operating-mode.html#how-the-one-folder-program-works):

pyinstaller ^
--paths "C:\Users\user\Documents\boxcel_exe\img-with-box-from-excel\src\boxcel" ^
--add-data "C:\Users\user\Documents\boxcel_exe\img-with-box-from-excel\src\boxcel\display_images.py;." ^
--noconsole ^
--name boxcel ^
--debug all ^
"C:\Users\user\Documents\boxcel_exe\img-with-box-from-excel\src\boxcel\gui.py"