Installing python modules hangs the UI

Context of Issue
I am trying to build a python extension that needs to install python libraries upon loading.
I have the following requirements

  1. Install and load modules via code
  2. Show a progress bar and do not hang the UI while performing Step 1

Status of Issue

  • Step 1 - I am able to use slicer.util.pip_install(’’) to successfully install the library in the python belonging to slicer
  • Step 2 - I am able to show the progress bar by using qt.QProgressDialog() (ref). Unfortunately while loading the module does not hang the UI, installing it does so. Installing a module via the python interpreter does the same thing.

Since I plan to share this extension with physicians, the UI-hanging part could be a deterrent in adoption of the extension. Is there a way around it so that I dont block the UI thread?

Thanks for raising this issue. I’ve also found that I can easily end up with Python downloading hundreds of megabytes of packages and installing them quite slowly, so I agree that it could make sense to indicate progress to users. We implemented pip_install function with the exact purpose of allowing adding features such as progress reporting on GUI, and probably time has arrived to implement it.

It should be as easy as creating a new variant of logProcessOutput(proc), which would show messages in a GUI window instead of just in Python console. Name of this function could be displayProcessOutput(proc) and make _executePythonModule use it instead of logProcessOutput when requested (e.g., introduce a new showWindow=True argument for _executePythonModule and pip_install).

This new displayProcessOutput(proc) function would be almost exactly the same as logProcessOutput, but it would create a progress dialog and show the name of current operation instead of logging stdout, and hide the dialog when the process is finished.

You can create a progress bar with busy indicator like this:

progressbar = slicer.util.createProgressDialog(autoClose=False)
progressbar.minimum=0
progressbar.maximum=0

You can set label text by calling:

progressbar.setLabelText(currentOperation)

Probably we should hide “Cancel” button, because although we could kill the install process, it might leave the package list in an inconsistent state.

We could try to show an actual progress bar instead of a busy indicator (infinite progress bar), but since even Navigator (Anaconda’s GUI client for installing python packages) does not do this, it might be too ambitious.

You can implement and test all these changes by simply editing c:\Users\<username>\AppData\Local\NA-MIC\Slicer 4.11.0-<date>\bin\Python\slicer\util.py file in a recent Slicer installation. If you have any questions then let us know. Thanks for working on this!

Thanks @lassoan for your response. I have taken a look at the functions you mentioned.
But I think we have different goals here.

I simply do not want the app to freeze while using “subprocess.Popen()” in “utils.launchConsoleProcess()” as faced by many others (ref1, ref2). To further elucidate my point, I have attached a screenshot. Notice the “Not responding” error on the top bar of the window as it has downloaded the package and is moving on to installing it. A solution for this is to use “QProcess”.

On the other hand, you’ve proposed a progress bar (which could come in handy for large libraries). But will it prevent the app from freezing up?

I would preferably want the user to be able to do other things as pip does its thing (like loading files etc.)

The application is blocked by this loop in logProcessOutput because we don’t want the user to do things while installation is in progress. Running pip install while the user is allowed to do anything in the application would be similar to replacing wheels on your car while you are driving it.

If you are confident that in your module installation does not usually lead to problems (you just install additional packages, no existing packages gets updated) then you can simply remove the loop or show progress bar on the status bar instead of a popup window to not block the GUI.

You probably still want to to make sure the user does not exit the application before the installation is completed and check for the final installation results. You can keep checking the process output every now and then, for example set up a repeating timer for 10sec and check the process output in the callback function.

I agree that a one-time blocking install with a spinning progress par would be completely normal. Could have a message like “Completing installation” or similar.

You might also want to send the lines to the status bar, just so people can get a sense that things are going on without needing to open the python console. You can add something like this to the logging line.

slicer.util.mainWindow().statusBar().showMessage("progress")
1 Like