Packaging a PsychoPy experiment with pyinstaller : it works!

Hello everyone !

I recently started making a PsychoPy experiment, and quickly got on packaging the experiment so that I can distribute it to people conveniently. I can’t use the JS version because I have incompatible components in my experiment, and asking people to download PsychoPy on their computers is just not possible in my case.

Since I have already some experience with pyinstaller, I wanted to try with my experiment. After -much- googling and hair pulling, I managed to do it ! Here are the few caveats I encountered, hoping it can help people in my situation.

Here is the script I use on my Windows machine (the ^ character is used for breaking down the command over several lines) :

"C:\Program Files\PsychoPy\python.exe" -m PyInstaller ^
    --onefile ^
    --add-binary "freetype.dll;." ^
    --add-data "C:\Program Files\PsychoPy\Lib\site-packages\psychopy;psychopy" ^
    --add-data "*.csv;." ^
    --add-data "AudioSamples;AudioSamples" ^
    .\sound_test.py

Let’s break it down for clarity :

"C:\Program Files\PsychoPy\python.exe" -m PyInstaller ^

It is mandatory to use the same Python version as PsychoPy to launch pyinstaller, otherwise it doesn’t work. Also, case matters for PyInstaller !

    --onefile ^

This option makes pyinstaller generate a single executable file

    --add-binary "freetype.dll;." ^

This line is the result of much hair pulling. The freetype module needs to load an external DLL that is not included automatically by pyinstaller. WARNING: it must be 64 bit ! At least in my case I guess, because I have a 64-bit machine and I’m trying to generate a 64-bit executable. You can compile the DLL from source, or use a pre-compiled binary. Then, place it in the same folder as the experiment Python file.

    --add-data "C:\Program Files\PsychoPy\Lib\site-packages\psychopy;psychopy" ^

This line was required for me because I use the standalone PsychoPy package. If you installed PsychoPy through pip, you may be able to delete it as it’s possible pyinstaller will include it automatically : to be tested.

    --add-data "*.csv;." ^
    --add-data "AudioSamples;AudioSamples" ^

Both these lines are specific to my experiment : these are files that are loaded at runtime.

    .\sound_test.py

Finally, this is the Python file that is compiled in the PsychoPy builder.

Important note : I could never figure out how to make the ioHub backend work for input purposes, so I had to switch to Psychtoolbox in the Experiment settings in the PsychoPy builder.

I hope this will help some people in my situation. And by the way, thanks for the awesome software !

Hello, here is an update on packaging. I encountered the following problem:

    --onefile ^

This option makes the whole executable into one file, which means that on execution, it self-extracts into the OS’ temporary folder before actually executing. Unfortunately, at the end of execution, this whole temporary folder is deleted, which means that the output data is deleted too. I see two solutions to this problem:

  1. Remove the --onefile option : Instead of a single file that self-extracts, the output is the entire folder. That means that it needs to be zipped before being sent to people. The drawback of this solution is that people will have to deal with a -very- cluttered folder to launch the executable and to find the data folder afterwards to send back to me

  2. Keep the --onefile option : In this case, I will have to add a code component to be executed at the very end of the experiment, in order to copy the output data folder to the executable’s directory or anywhere else that is convenient. Relevant documentation to achieve this can be found here

Option 2 would be the best IMO, because it is more user-friendly : however, after some testing it looks like it’s not possible in the current state of things. I added the following snippet in a Code component to be executed at the end of the experiment :

if getattr(sys, "frozen", False):
    # We are in a PyInstaller bundle, so we need to gather the data to avoid
    # deletion in the case of a single-file bundle

    executable_dir = os.path.dirname(os.path.abspath(sys.executable))
    bundle_dir = os.path.abspath(sys._MEIPASS)
    
    data_dir = os.path.join(bundle_dir, "data")
    target_file = os.path.join(executable_dir, "data")
    
    from shutil import make_archive
    make_archive(target_file, "zip", data_dir)

The goal is to zip the data folder and place it next to the executable file so that it’s convenient for the user to send back to me. The problem is, the CSV and psydat files are not present in that zip file, because I guess that they are generated after the “End experiment” code blocks.

So the final solutions for this are :

  1. Remove the --onefile option : this will bring the drawbacks mentioned earlier
  2. Keep the --onefile option and use the Coder to add the above snippet at the very end of the Python file : this is not an option for me as I want to use the Builder
  3. Keep the --onefile option and add the above snippet “manually” by adding some instructions in the pyinstaller script from my first post

I haven’t decided which option to choose between 1 and 3 yet, but I figured it would be useful to other people to document my thought process here.

Hello!

I am a psychology student currently working for my Uni. I have to turn a psychopy experiment into a .exe file, and would like to thank you for your blog post. Without this post I don’t think I’d have been able to get where I am now.

thank you.

1 Like