Napari from jupyter notebook: 'viewer' variable automatically created?

Hi,

I’m following the napari tutorials at the moment, and the viewer (+plus the documentation) looks great!

There is however a funny thing that happens when I run from jupyter notebook, this is a minimal example:

from skimage import data
import napari

with napari.gui_qt():
    myviewer=napari.Viewer()
    myviewer.add_image(data.astronaut(),rgb=True)

    print(myviewer)
    print(viewer) # should not exist?

A variable named viewer is automatically created and seems to point always to the latest viewer instance that I create.
(I had actually I created more than one viewer instance, named the first one “viewer” and lost access to it as soon as another instance was created).

This does not happen if the same script is run from terminal in the style python myscript.py

Any ideas what could be happening? Thanks already!

1 Like

The viewer variable is created as a convenience when instantiating a napari.Viewer. The “convenience” here is that a user can open the shell embedded in the napari GUI (bottom left icon) and immediately have the viewer instance available as viewer.

The code behind this:
napari.Viewer() creates a QtViewer() instance, which in turn creates a QtConsole() instance with an ipython kernel that has been prepopulated with a "viewer" variable pointing to the viewer instance.

When running napari in jupyter notebook, QtConsole will find the existing ipython instance and add viewer to it, which is why you observe the behavior your do (also note that myviewer and viewer have the same memory id in your example). When you run it as a python myscript.py however, it does not find an existing ipython instance and so the newly created viewer variable will only be found in the ipython kernel available within napari, but not in the global python interpreter, so your print(viewer) statement will fail.

1 Like

@talley gives a good explanation of how that viewer object gets named and created. The motivation was to always gives users a consistently named object inside the console we provide, but as @noreenw noted this can also lead to some unexpected consequences, particularly if launching multiple viewers simultaneously. We can potentially lessen that by asking if a variable named viewer already exists, and if it is identical to the variable we want to create - if not, we could make a new one like viewer_1. Not sure if that is more desirable. I think we’re pretty open to improvements here

1 Like

Thanks @talley and @sofroniewn for the explanation. That really clears things up!

From my personal feeling it would be more intuitive if the viewer variable in a notebook cannot be reassigned to the next created Viewer instance. But not sure how this would fit at all into the bigger framework.

2 Likes

I think Ahmet Can has shown us an interesting approach in #650. The motivation for putting viewer in was that, when the viewer is created, we don’t know what variable it is going to be assigned to. This is why we have the method update_console, demonstrated in this example. So, we decided to just assume viewer for now. I was never super happy with that approach.

But, maybe we can launch a QThread that monitors the locals() namespace and keeps the viewer console up to date? I just checked, and locals() returns a reference to the dictionary, not a copy, so it should be possible to monitor this periodically.

The super quick fix is to use _this_viewer as the variable, so that users who don’t use update_console always have access to it, but know that the canonical way to access the viewer is through that update.

1 Like