An introduction to the event loop in napari#
Brief summary#
It is not necessary to have a deep understanding of Qt or event loops to use napari. napari attempts to use “sane defaults” for most scenarios. Here are the most important details:
In IPython or Jupyter Notebook#
napari will detect if you are running an IPython or Jupyter shell, and will
automatically use the IPython GUI event
loop.
As of version 0.4.7, it is
no longer necessary to call %gui qt
manually. Just create a viewer:
In [1]: import napari
In [2]: viewer = napari.Viewer() # Viewer will show in a new window
In [3]: ... # Continue interactive usage
Tip
If you would prefer that napari did not start the interactive event loop for you in IPython, then you can disable it with:
from napari.settings import get_settings
get_settings().application.ipy_interactive = False
… but then you will have to start the program yourself as described below.
In a script#
Outside of IPython, you must tell napari when to “start the program” using
napari.run()
. This will block execution of your script at that point,
show the viewer, and wait for any user interaction. When the last viewer
closes, execution of the script will proceed.
import napari
viewer = napari.Viewer()
... # Continue setting up your program
# Start the program, show the viewer, wait for GUI interaction.
napari.run()
# Anything below here will execute only after the viewer is closed.
More in depth…#
Like most applications with a graphical user interface (GUI), napari operates within an event loop that waits for – and responds to – events triggered by the user’s interactions with the interface. These events might be something like a mouse click, or a keypress, and usually correspond to some specific action taken by the user (e.g. “user moved the gamma slider”).
At its core, an event loop is rather simple. It amounts to something that looks like this (in pseudo-code):
event_queue = Queue()
while True: # infinite loop!
if not event_queue.is_empty():
event = get_next_event()
if event.value == 'Quit':
break
else:
process_event(event)
Actions taken by the user add events to the queue (e.g. “button pressed”, “slider moved”, etc…), and the event loop handles them one at a time.
Qt applications and event loops#
Currently, napari uses Qt as its GUI backend, and the main loop handling events in napari is the Qt EventLoop.
A deep dive into the Qt event loop is beyond the scope of this document, but it’s worth being aware of two critical steps in the “lifetime” of a Qt Application:
Any program that would like to create a
QWidget
(the class from which all napari’s graphical elements are subclassed), must create aQApplication
instance before instantiating any widgets.from qtpy.QtWidgets import QApplication app = QApplication([]) # where [] is a list of args passed to the App
In order to actually show and interact with widgets, one must start the application’s event loop:
app.exec_()
napari’s QApplication
#
In napari, the initial step of creating the QApplication
is handled by
napari.qt.get_app()
. (Note however, that napari will do this for you
automatically behind the scenes when you create a viewer with
napari.Viewer()
)
The second step – starting the Qt event loop – is handled by napari.run()
import napari
viewer = napari.Viewer() # This will create a Application if one doesn't exist
napari.run() # This will call `app.exec_()` and start the event loop.
What about napari.gui_qt
?
napari.gui_qt()
was deprecated in version 0.4.8.
The autocreation of the QApplication
instance and the napari.run()
function was introduced in
PR#2056, and released in version
0.4.3. Prior to that,
all napari examples included this gui_qt()
context manager:
# deprecated
with napari.gui_qt():
viewer = napari.Viewer()
On entering the context, gui_qt
would create a QApplication
, and on exiting
the context, it would start the event loop (the two critical steps mentioned
above).
Unlike a typical context manager, however, it did not actually destroy the
QApplication
(since it may still be needed in the same session)… and future
calls to gui_qt
were only needed to start the event loop. By auto-creating
the QApplication
during Viewer
creation, introducing the
explicit napari.run()
function, and using the integrated IPython event
loop when applicable, we hope to simplify the
usage of napari.
Now that you have an understanding of how napari creates the event loop, you may wish to learn more about hooking up your own actions and callbacks to specific events.