Repository: napari/napari

Issue:
Weird docs build error on vortex.py example
### 🐛 Bug Report

It looks like we are missing the gallery example for [vortex.py](https://github.com/napari/napari/blob/1b510bab020aae485565f000ddab842ab17ef608/examples/vortex.py). That file runs fine locally and *I think* ~~it ran fine in the PR~~ (**Edit:** nope, it was [broken](https://output.circle-artifacts.com/output/job/1b750fb4-4df5-462b-84ca-cdddeede41ff/artifacts/0/napari-docs/docs/_build/gallery.html#gallery), but gallery errors aren't errors. I don't know what the right answer is there but either we should turn them into errors or we should turn them into errors *when the contribution is a gallery example*?) But the error suggests some form of race condition during addition of the layer, which makes me think this is an async issue. Here's the error message from [this build](https://github.com/napari/docs/actions/runs/6658042739/job/18094063805#step:9:238):

```pytb
Downloading file 'data/pivchallenge-B-B001_1.tif' from 'https://gitlab.com/scikit-image/data/-/raw/2cdc5ce89b334d28f06a58c9f0ca21aa6992a5ba/pivchallenge/B/B001_1.tif' to '/home/runner/.cache/scikit-image/0.22.0'.
Downloading file 'data/pivchallenge-B-B001_2.tif' from 'https://gitlab.com/scikit-image/data/-/raw/2cdc5ce89b334d28f06a58c9f0ca21aa6992a5ba/pivchallenge/B/B001_2.tif' to '/home/runner/.cache/scikit-image/0.22.0'.
WARNING: /home/runner/work/docs/docs/docs/examples/vortex.py failed to execute correctly: Traceback (most recent call last):
  File "/home/runner/work/docs/docs/docs/examples/vortex.py", line 59, in <module>
    flow_layer = viewer.add_vectors(
  File "/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/napari/components/viewer_model.py", line 5, in add_vectors
    import os
  File "/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/_collections_abc.py", line 1128, in append
    self.insert(len(self), value)
  File "/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/napari/components/layerlist.py", line 194, in insert
    super().insert(index, new_layer)
  File "/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/napari/utils/events/containers/_selectable_list.py", line 71, in insert
    self.selection.active = value
  File "/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/napari/utils/events/containers/_selection.py", line 108, in active
    self.events.active(value=value)
  File "/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/napari/utils/events/event.py", line 771, in __call__
    self._invoke_callback(cb, event if pass_event else None)
  File "/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/napari/utils/events/event.py", line 809, in _invoke_callback
    _handle_exception(
  File "/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/napari/utils/events/event.py", line 796, in _invoke_callback
    cb(event)
  File "/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/napari/_qt/layer_controls/qt_layer_controls_container.py", line 130, in _display
    controls = self.widgets[layer]
KeyError: <Vectors layer 'optical flow' at 0x7f6f55be4be0>
```

### 💡 Steps to Reproduce

I don't have a working docs build at the moment so I don't know whether this reproduces in local docs builds, but the example runs fine locally. So this is either a problem with sphinx gallery or with sphinx gallery on CI.

### 💡 Expected Behavior

Example should run fine on GHA.

### 🌎 Environment

napari main on CI 😬 

(Note: should we echo `napari --info` on all our CI?)

### 💡 Additional Context

_No response_


Discussion:
I got this same thing when building docs locally. Just in case:
```
WARNING: /Users/piotrsobolewski/Dev/napari/examples/vortex.py failed to execute correctly: Traceback (most recent call last):
  File "/Users/piotrsobolewski/Dev/napari/examples/vortex.py", line 59, in <module>
    flow_layer = viewer.add_vectors(
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/nap-docs/lib/python3.10/site-packages/napari/components/viewer_model.py", line 5, in add_vectors
    import os
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/nap-docs/lib/python3.10/_collections_abc.py", line 1123, in append
    self.insert(len(self), value)
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/nap-docs/lib/python3.10/site-packages/napari/components/layerlist.py", line 194, in insert
    super().insert(index, new_layer)
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/nap-docs/lib/python3.10/site-packages/napari/utils/events/containers/_selectable_list.py", line 71, in insert
    self.selection.active = value
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/nap-docs/lib/python3.10/site-packages/napari/utils/events/containers/_selection.py", line 108, in active
    self.events.active(value=value)
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/nap-docs/lib/python3.10/site-packages/napari/utils/events/event.py", line 771, in __call__
    self._invoke_callback(cb, event if pass_event else None)
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/nap-docs/lib/python3.10/site-packages/napari/utils/events/event.py", line 809, in _invoke_callback
    _handle_exception(
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/nap-docs/lib/python3.10/site-packages/napari/utils/events/event.py", line 796, in _invoke_callback
    cb(event)
  File "/Users/piotrsobolewski/Dev/miniforge3/envs/nap-docs/lib/python3.10/site-packages/napari/_qt/layer_controls/qt_layer_controls_container.py", line 130, in _display
    controls = self.widgets[layer]
KeyError: <Vectors layer 'optical flow' at 0x28ee29de0>
```
Actually, my blaming async might have been a red herring — it might be events not being processed correctly when running in the gallery?
Hi there, gave this a check and seems like putting all the example code inside `if __name__ == '__main__'` and formatting a little bit the docstring helps. At least locally for me those changes enable the docs build to not show an error for the example and I'm able to go to the related example page:

![gallery_examples_list_page](https://github.com/napari/napari/assets/16781833/0e65f2b3-c3f8-48c4-b348-8c97c3af39a5)

![vortex_example_page](https://github.com/napari/napari/assets/16781833/8586d60b-36cc-4d8a-94e4-ddfd5e1ef2ff)

The `vortex.py` example ends up being something like:

```python
"""
Visualizing optical flow in napari.
===================================

Adapted from the scikit-image gallery [1]_.

In napari, we can show the flowing vortex as an additional dimension in the
image, visible by moving the slider.

.. tags:: visualization-advanced, layers

.. [1] https://scikit-image.org/docs/stable/auto_examples/registration/plot_opticalflow.html
"""
import numpy as np
from skimage.data import vortex
from skimage.registration import optical_flow_ilk

import napari


if __name__ == '__main__':
    #######################################################################
    # First, we load the vortex image as a 3D array. (time, row, column)

    vortex_im = np.asarray(vortex())

    #######################################################################
    # We compute the optical flow using scikit-image. (Note: as of
    # scikit-image 0.21, there seems to be a transposition of the image in
    # the output, which we account for later.)

    u, v = optical_flow_ilk(vortex_im[0], vortex_im[1], radius=15)

    #######################################################################
    # Compute the flow magnitude, for visualization.

    magnitude = np.sqrt(u ** 2 + v ** 2)
    #######################################################################
    # Create a viewer, add the vortex frames, and overlay the flow
    # magnitude.

    viewer, vortex_layer = napari.imshow(vortex_im)
    mag_layer = viewer.add_image(magnitude, colormap='magma', opacity=0.3)

    #######################################################################
    # Finally, we subsample the vector field to display it — it's too
    # messy otherwise! And we transpose the rows/columns axes to match the
    # current scikit-image output.

    nvec = 21
    nr, nc = magnitude.shape
    step = max(nr//nvec, nc//nvec)
    offset = step // 2
    usub = u[offset::step, offset::step]
    vsub = v[offset::step, offset::step]

    vectors_field = np.transpose(  # transpose required — skimage bug?
            np.stack([usub, vsub], axis=-1),
            (1, 0, 2),
            )

    flow_layer = viewer.add_vectors(
            vectors_field,
            name='optical flow',
            scale=[step, step],
            translate=[offset, offset],
            edge_width=0.3,
            length=0.3,
            )

    napari.run()

```
Hmmm I don't love it... Would love to understand *why*! It's weird, right???
Yep, weird indeed, checked again and not totally sure but maybe the error could be caused due to the `napari_scraper` definition. There, after a screenshot is done, all viewer instances are being closed:

https://github.com/napari/docs/blob/1a3192fd7fc549b4ef8730dccae8bcbc673096c9/docs/conf.py#L219-L242

Not totally sure exactly how the iteration is done but most probably for each section defined by `#######################################################################` in an example a screenshot will be taken and after that any open viewer will be closed (making code in other section that uses the closed viewer to error)

Taking the above into account, if I duplicate the viewer related lines in the last layer addition section (when adding the vectors field) making the example code to be something like:

```python
"""
Visualizing optical flow in napari
==================================

Adapted from the scikit-image gallery [1]_.

In napari, we can show the flowing vortex as an additional dimension in the
image, visible by moving the slider.

.. tags:: visualization-advanced, layers

.. [1] https://scikit-image.org/docs/stable/auto_examples/registration/plot_opticalflow.html
"""
import numpy as np
from skimage.data import vortex
from skimage.registration import optical_flow_ilk

import napari


#######################################################################
# First, we load the vortex image as a 3D array. (time, row, column)

vortex_im = np.asarray(vortex())

#######################################################################
# We compute the optical flow using scikit-image. (Note: as of
# scikit-image 0.21, there seems to be a transposition of the image in
# the output, which we account for later.)

u, v = optical_flow_ilk(vortex_im[0], vortex_im[1], radius=15)

#######################################################################
# Compute the flow magnitude, for visualization.

magnitude = np.sqrt(u ** 2 + v ** 2)

#######################################################################
# Create a viewer, add the vortex frames, and overlay the flow
# magnitude.

viewer, vortex_layer = napari.imshow(vortex_im)
mag_layer = viewer.add_image(magnitude, colormap='magma', opacity=0.3)

#######################################################################
# Finally, we subsample the vector field to display it — it's too
# messy otherwise! And we transpose the rows/columns axes to match the
# current scikit-image output.

nvec = 21
nr, nc = magnitude.shape
step = max(nr//nvec, nc//nvec)
offset = step // 2
usub = u[offset::step, offset::step]
vsub = v[offset::step, offset::step]

vectors_field = np.transpose(  # transpose required — skimage bug?
        np.stack([usub, vsub], axis=-1),
        (1, 0, 2),
        )

viewer, vortex_layer = napari.imshow(vortex_im)
mag_layer = viewer.add_image(magnitude, colormap='magma', opacity=0.3)
flow_layer = viewer.add_vectors(
        vectors_field,
        name='optical flow',
        scale=[step, step],
        translate=[offset, offset],
        edge_width=0.3,
        length=0.3,
        )

if __name__ == '__main__':

    napari.run()

```

I'm able to build the docs without errors and see the following structure for the vortex example page:

![vortex_docs](https://github.com/napari/napari/assets/16781833/0a84bcc2-7b9b-407d-aa2f-cfd356646212)

Seems like putting all the code inside the `if __name__ == '__main__':` made the screenshots iterator to handle the code as it being a unique section which then triggers only taking one screenshot (the final result where the viewer has all three layers already setup/added)

Oh good call! 
The lines of `#` are turning this into a notebook rather than just a regular python script:
https://sphinx-gallery.github.io/stable/tutorials/plot_parse.html

I bet deleting those lines would also fix the issue.
> I bet deleting those lines would also fix the issue.

Yep, checked and that also enables to run the example and generate the docs page (although you get only one screenshot of the final state and no sections division/comments to be rendered as docs text):

![vortex_docs_one](https://github.com/napari/napari/assets/16781833/3647968a-86ef-481c-996f-8679104986e7)



Resolving patch:
diff --git a/examples/vortex.py b/examples/vortex.py
--- a/examples/vortex.py
+++ b/examples/vortex.py
@@ -1,4 +1,6 @@
-"""Visualizing optical flow in napari.
+"""
+Visualizing optical flow in napari
+==================================
 
 Adapted from the scikit-image gallery [1]_.
 
@@ -33,14 +35,7 @@
 magnitude = np.sqrt(u ** 2 + v ** 2)
 
 #######################################################################
-# Create a viewer, add the vortex frames, and overlay the flow
-# magnitude.
-
-viewer, vortex_layer = napari.imshow(vortex_im)
-mag_layer = viewer.add_image(magnitude, colormap='magma', opacity=0.3)
-
-#######################################################################
-# Finally, we subsample the vector field to display it — it's too
+# We subsample the vector field to display it — it's too
 # messy otherwise! And we transpose the rows/columns axes to match the
 # current scikit-image output.
 
@@ -56,6 +51,12 @@
         (1, 0, 2),
         )
 
+#######################################################################
+# Finally, we create a viewer, and add the vortex frames, the flow
+# magnitude, and the vector field.
+
+viewer, vortex_layer = napari.imshow(vortex_im)
+mag_layer = viewer.add_image(magnitude, colormap='magma', opacity=0.3)
 flow_layer = viewer.add_vectors(
         vectors_field,
         name='optical flow',


Test patch: