def test_multi_viewers_dont_clash(qapp):
    v1 = Viewer(show=False, title='v1')
    v2 = Viewer(show=False, title='v2')
    assert not v1.grid.enabled
    assert not v2.grid.enabled

    v1.window.activate()  # a click would do this in the actual gui
    v1.window._qt_viewer.viewerButtons.gridViewButton.click()

    assert not v2.grid.enabled
    assert v1.grid.enabled

    v1.close()
    v2.close()
def test_range_one_images_and_points(qtbot):
    """Test adding images with range one dimensions and points.

    Intially no sliders should be present as the images have range one
    dimensions. On adding the points the sliders should be displayed.
    """
    np.random.seed(0)
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    # add 5D image data with range one dimensions
    data = np.random.random((1, 1, 1, 100, 200))
    viewer.add_image(data)
    assert np.all(viewer.layers[0].data == data)
    assert len(viewer.layers) == 1
    assert viewer.dims.ndim == 5
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 0

    # now add 5D points data - check extra sliders have been created
    points = np.floor(5 * np.random.random((1000, 5))).astype(int)
    points[:, -2:] = 20 * points[:, -2:]
    viewer.add_points(points)
    assert np.all(viewer.layers[1].data == points)
    assert len(viewer.layers) == 2
    assert viewer.dims.ndim == 5
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 3

    # Close the viewer
    viewer.window.close()
Exemple #3
0
def test_5D_image_3D_rendering(qtbot):
    """Test 3D rendering of a 5D image."""
    np.random.seed(0)
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    # add 4D image data
    data = np.random.random((2, 10, 12, 13, 14))
    viewer.add_image(data)
    assert np.all(viewer.layers[0].data == data)
    assert len(viewer.layers) == 1
    assert viewer.dims.ndim == 5
    assert viewer.dims.ndisplay == 2
    assert viewer.layers[0]._data_view.ndim == 2
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 3

    # switch to 3D rendering
    viewer.dims.ndisplay = 3
    assert viewer.dims.ndisplay == 3
    assert viewer.layers[0]._data_view.ndim == 3
    assert np.sum(view.dims._displayed_sliders) == 2

    # Close the viewer
    viewer.window.close()
Exemple #4
0
def test_add_image(qtbot):
    """Test adding image."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    np.random.seed(0)
    data = np.random.random((10, 15))
    viewer.add_image(data)
    assert np.all(viewer.layers[0].data == data)

    assert len(viewer.layers) == 1
    assert view.layers.vbox_layout.count() == 2 * len(viewer.layers) + 2

    assert viewer.dims.ndim == 2
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 0

    # Switch to 3D rendering mode and back to 2D rendering mode
    viewer.dims.ndisplay = 3
    assert viewer.dims.ndisplay == 3
    viewer.dims.ndisplay = 2
    assert viewer.dims.ndisplay == 2

    # Close the viewer
    viewer.window.close()
Exemple #5
0
def test_data_change_ndisplay_surface(qtbot):
    """Test change data calls for surface layer with ndisplay change."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    np.random.seed(0)
    vertices = np.random.random((10, 3))
    faces = np.random.randint(10, size=(6, 3))
    values = np.random.random(10)
    data = (vertices, faces, values)
    layer = viewer.add_surface(data)

    visual = view.layer_to_visual[layer]

    @patch.object(visual, '_on_data_change', wraps=visual._on_data_change)
    def test_ndisplay_change(mocked_method, ndisplay=3):
        viewer.dims.ndisplay = ndisplay
        mocked_method.assert_called_once()

    # Switch to 3D rendering mode and back to 2D rendering mode
    test_ndisplay_change(ndisplay=3)
    test_ndisplay_change(ndisplay=2)

    # Close the viewer
    viewer.window.close()
Exemple #6
0
 def actual_factory(*model_args, **model_kwargs):
     model_kwargs['show'] = model_kwargs.pop(
         'show', request.config.getoption("--show-viewer")
     )
     viewer = Viewer(*model_args, **model_kwargs)
     viewers.append(viewer)
     return viewer
Exemple #7
0
def test_update(qtbot):
    import time

    data = np.random.random((512, 512))
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    layer = viewer.add_image(data)

    def layer_update(*, update_period, num_updates):
        # number of times to update

        for k in range(num_updates):
            time.sleep(update_period)

            dat = np.random.random((512, 512))
            layer.data = dat

            assert layer.data.all() == dat.all()

    viewer.update(layer_update, update_period=0.01, num_updates=100)

    # if we do not sleep, main thread closes before update thread finishes and many qt components get cleaned
    time.sleep(3)

    # Close the viewer
    viewer.window.close()
Exemple #8
0
def test_run_outside_ipython(qapp, monkeypatch):
    """Test that we don't incorrectly give ipython the event loop."""
    assert not _ipython_has_eventloop()
    v1 = Viewer(show=False)
    assert not _ipython_has_eventloop()
    v2 = Viewer(show=False)
    assert not _ipython_has_eventloop()

    with monkeypatch.context() as m:
        mock_exec = Mock()
        m.setattr(qapp, 'exec_', mock_exec)
        run()
        mock_exec.assert_called_once()

    v1.close()
    v2.close()
Exemple #9
0
def test_nD_volume_launch(qtbot):
    """Test adding nD volume when viewer launched with 3D."""
    viewer = Viewer(ndisplay=3)
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    np.random.seed(0)
    shape = (6, 10, 15, 20)
    data = np.random.random(shape)
    viewer.add_image(data)
    assert np.all(viewer.layers[0].data == data)
    assert viewer.layers[0]._data_view.shape == shape[-3:]

    assert len(viewer.layers) == 1
    assert view.layers.vbox_layout.count() == 2 * len(viewer.layers) + 2
    assert viewer.dims.ndim == 4
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 1

    # Switch to 3D rendering
    assert viewer.dims.ndisplay == 3
    viewer.dims.ndisplay = 2
    assert viewer.dims.ndisplay == 2
    # Close the viewer
    viewer.window.close()
Exemple #10
0
def test_nD_shapes(qtbot):
    """Test adding vectors."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    np.random.seed(0)
    # create one random rectangle per "plane"
    planes = np.tile(np.arange(10).reshape((10, 1, 1)), (1, 4, 1))
    corners = np.random.uniform(0, 10, size=(10, 4, 2))
    data = np.concatenate((planes, corners), axis=2)
    viewer.add_shapes(data)
    assert np.all(
        [np.all(ld == d) for ld, d in zip(viewer.layers[0].data, data)]
    )
    assert len(viewer.layers) == 1
    assert view.layers.vbox_layout.count() == 2 * len(viewer.layers) + 2

    assert viewer.dims.ndim == 3
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 1

    # Flip dims order displayed
    viewer.dims.order = [0, 2, 1]
    assert viewer.dims.order == [0, 2, 1]

    # Flip dims order including non-displayed
    viewer.dims.order = [1, 0, 2]
    assert viewer.dims.order == [1, 0, 2]

    # Close the viewer
    viewer.window.close()
Exemple #11
0
def test_nD_volume_launch_order(qtbot):
    """Test adding nD volume when viewer launched with 3D."""
    order = [1, 0, 2, 3]
    viewer = Viewer(ndisplay=3, order=order)
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    np.random.seed(0)
    shape = (6, 10, 15, 20)
    data = np.random.random(shape)
    viewer.add_image(data)
    assert np.all(viewer.layers[0].data == data)
    assert viewer.layers[0]._data_view.shape == tuple(
        shape[o] for o in order[-3:]
    )

    assert len(viewer.layers) == 1
    assert view.layers.vbox_layout.count() == 2 * len(viewer.layers) + 2
    assert viewer.dims.ndim == 4
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 1
    assert viewer.dims.order == order

    # Close the viewer
    viewer.window.close()
Exemple #12
0
def test_nD_vectors(qtbot):
    """Test adding nD vectors."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    np.random.seed(0)
    data = 20 * np.random.random((10, 2, 3))
    viewer.add_vectors(data)
    assert np.all(viewer.layers[0].data == data)

    assert len(viewer.layers) == 1
    assert view.layers.vbox_layout.count() == 2 * len(viewer.layers) + 2

    assert viewer.dims.ndim == 3
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 1

    # Flip dims order displayed
    viewer.dims.order = [0, 2, 1]
    assert viewer.dims.order == [0, 2, 1]

    # Flip dims order including non-displayed
    viewer.dims.order = [1, 0, 2]
    assert viewer.dims.order == [1, 0, 2]

    # Close the viewer
    viewer.window.close()
Exemple #13
0
def test_image_rendering(qtbot):
    """Test 3D image with different rendering."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    data = np.random.random((20, 20, 20))
    layer = viewer.add_image(data)

    assert layer.rendering == 'mip'

    # Change rendering property
    layer.rendering = 'translucent'
    assert layer.rendering == 'translucent'

    # Change rendering property
    layer.rendering = 'attenuated_mip'
    assert layer.rendering == 'attenuated_mip'
    layer.attenuation = 0.2
    assert layer.attenuation == 0.2

    # Change rendering property
    layer.rendering = 'iso'
    assert layer.rendering == 'iso'
    layer.iso_threshold = 0.3
    assert layer.iso_threshold == 0.3

    # Change rendering property
    layer.rendering = 'additive'
    assert layer.rendering == 'additive'

    # Close the viewer
    viewer.window.close()
def test_4D_5D_images(qtbot):
    """Test adding 4D followed by 5D image layers to the viewer.

    Intially only 2 sliders should be present, then a third slider should be
    created.
    """
    np.random.seed(0)
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    # add 4D image data
    data = np.random.random((2, 6, 30, 40))
    viewer.add_image(data)
    assert np.all(viewer.layers[0].data == data)
    assert len(viewer.layers) == 1
    assert viewer.dims.ndim == 4
    assert view.dims.nsliders == 2
    assert np.sum(view.dims._displayed) == 2

    # now add 5D image data - check an extra slider has been created
    data = np.random.random((4, 4, 5, 30, 40))
    viewer.add_image(data)
    assert np.all(viewer.layers[1].data == data)
    assert len(viewer.layers) == 2
    assert viewer.dims.ndim == 5
    assert view.dims.nsliders == 3
    assert np.sum(view.dims._displayed) == 3

    # Close the viewer
    viewer.window.close()
Exemple #15
0
def test_add_volume(qtbot):
    """Test adding volume."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    np.random.seed(0)
    data = np.random.random((10, 15, 20))
    layer = viewer.add_image(data)
    viewer.dims.ndisplay = 3
    assert np.all(viewer.layers[0].data == data)

    assert len(viewer.layers) == 1
    assert view.layers.vbox_layout.count() == 2 * len(viewer.layers) + 2

    assert viewer.dims.ndim == 3
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 0

    # Switch to 3D rendering mode and back to 2D rendering mode
    viewer.dims.ndisplay = 3
    assert viewer.dims.ndisplay == 3
    viewer.dims.ndisplay = 2
    assert viewer.dims.ndisplay == 2

    # Run all class keybindings
    for func in layer.class_keymap.values():
        func(layer)

    # Close the viewer
    viewer.window.close()
Exemple #16
0
def test_nD_image(qtbot):
    """Test adding nD image."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    np.random.seed(0)
    data = np.random.random((6, 10, 15))
    viewer.add_image(data)
    assert np.all(viewer.layers[0].data == data)

    assert len(viewer.layers) == 1
    assert view.layers.vbox_layout.count() == 2 * len(viewer.layers) + 2

    assert viewer.dims.ndim == 3
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 1

    # Flip dims order displayed
    viewer.dims.order = [0, 2, 1]
    assert viewer.dims.order == [0, 2, 1]

    # Flip dims order including non-displayed
    viewer.dims.order = [1, 0, 2]
    assert viewer.dims.order == [1, 0, 2]

    # Switch to 3D rendering
    viewer.dims.ndisplay = 3
    assert np.sum(view.dims._displayed_sliders) == 0
    assert viewer.dims.ndisplay == 3
    viewer.dims.ndisplay = 2
    assert viewer.dims.ndisplay == 2

    # Close the viewer
    viewer.window.close()
Exemple #17
0
def test_screenshot(qtbot):
    "Test taking a screenshot"
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    np.random.seed(0)
    # Add image
    data = np.random.random((10, 15))
    viewer.add_image(data)

    # Add labels
    data = np.random.randint(20, size=(10, 15))
    viewer.add_labels(data)

    # Add points
    data = 20 * np.random.random((10, 2))
    viewer.add_points(data)

    # Add vectors
    data = 20 * np.random.random((10, 2, 2))
    viewer.add_vectors(data)

    # Add shapes
    data = 20 * np.random.random((10, 4, 2))
    viewer.add_shapes(data)

    # Take screenshot
    screenshot = viewer.screenshot()
    assert screenshot.ndim == 3

    # Close the viewer
    viewer.window.close()
Exemple #18
0
def test_viewer(qtbot):
    """Test instantiating viewer."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    assert viewer.title == 'napari'
    assert view.viewer == viewer

    assert len(viewer.layers) == 0
    assert view.layers.vbox_layout.count() == 2

    assert viewer.dims.ndim == 2
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 0

    # Switch to 3D rendering mode and back to 2D rendering mode
    viewer.dims.ndisplay = 3
    assert viewer.dims.ndisplay == 3
    viewer.dims.ndisplay = 2
    assert viewer.dims.ndisplay == 2

    # Run all class keybindings
    for func in viewer.class_keymap.values():
        func(viewer)

    # Close the viewer
    viewer.window.close()
Exemple #19
0
def test_add_pyramid(qtbot):
    """Test adding image pyramid."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    shapes = [(40, 20), (20, 10), (10, 5)]
    np.random.seed(0)
    data = [np.random.random(s) for s in shapes]
    layer = viewer.add_image(data, is_pyramid=True)
    assert np.all(viewer.layers[0].data == data)

    assert len(viewer.layers) == 1
    assert view.layers.vbox_layout.count() == 2 * len(viewer.layers) + 2

    assert viewer.dims.ndim == 2
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 0

    # Switch to 3D rendering mode and back to 2D rendering mode
    viewer.dims.ndisplay = 3
    assert viewer.dims.ndisplay == 3
    viewer.dims.ndisplay = 2
    assert viewer.dims.ndisplay == 2

    # Run all class keybindings
    for func in layer.class_keymap.values():
        func(layer)

    # Close the viewer
    viewer.window.close()
Exemple #20
0
def test_add_surface(qtbot):
    """Test adding 3D surface."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    np.random.seed(0)
    vertices = np.random.random((10, 3))
    faces = np.random.randint(10, size=(6, 3))
    values = np.random.random(10)
    data = (vertices, faces, values)
    layer = viewer.add_surface(data)
    assert np.all(
        [np.all(vd == d) for vd, d in zip(viewer.layers[0].data, data)])

    assert len(viewer.layers) == 1
    assert view.layers.vbox_layout.count() == 2 * len(viewer.layers) + 2

    assert viewer.dims.ndim == 3
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 1

    # Switch to 3D rendering mode and back to 2D rendering mode
    viewer.dims.ndisplay = 3
    assert viewer.dims.ndisplay == 3
    viewer.dims.ndisplay = 2
    assert viewer.dims.ndisplay == 2

    # Run all class keybindings
    for func in layer.class_keymap.values():
        func(layer)

    # Close the viewer
    viewer.window.close()
def test_range_one_image(qtbot):
    """Test adding an image with a range one dimensions.

    There should be no slider shown for the axis corresponding to the range
    one dimension.
    """
    np.random.seed(0)
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    # add 5D image data with range one dimensions
    data = np.random.random((1, 1, 1, 100, 200))
    viewer.add_image(data)
    assert np.all(viewer.layers[0].data == data)
    assert len(viewer.layers) == 1
    assert viewer.dims.ndim == 5
    assert view.dims.nsliders == 3
    assert np.sum(view.dims._displayed) == 0

    # now add 5D points data - check extra sliders have been created
    points = np.floor(5 * np.random.random((1000, 5))).astype(int)
    points[:, -2:] = 20 * points[:, -2:]
    viewer.add_points(points)
    assert np.all(viewer.layers[1].data == points)
    assert len(viewer.layers) == 2
    assert viewer.dims.ndim == 5
    assert view.dims.nsliders == 3
    assert np.sum(view.dims._displayed) == 3

    # Close the viewer
    viewer.window.close()
Exemple #22
0
def _run_plugin_module(mod, plugin_name):
    """Register `mod` as a plugin, find/create viewer, and run napari."""
    from napari.plugins import plugin_manager

    plugin_manager.register(mod, name=plugin_name)

    # now, check if a viewer was created, and if not, create one.
    for obj in mod.values():
        if isinstance(obj, Viewer):
            _v = obj
            break
    else:
        _v = Viewer()

    try:
        _v.window._qt_window.parent()
    except RuntimeError:
        # this script had a napari.run() in it, and the viewer has already been
        # used and cleaned up... if we eventually have "reusable viewers", we
        # can continue here
        return

    # finally, if the file declared a dock widget, add it to the viewer.
    dws = plugin_manager.hooks.napari_experimental_provide_dock_widget
    if any(i.plugin_name == plugin_name for i in dws.get_hookimpls()):
        _v.window.add_plugin_dock_widget(plugin_name)

    run()
Exemple #23
0
def test_nD_pyramid(qtbot):
    """Test adding nD image pyramid."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    shapes = [(8, 40, 20), (4, 20, 10), (4, 10, 5)]
    np.random.seed(0)
    data = [np.random.random(s) for s in shapes]
    viewer.add_pyramid(data)
    assert np.all(viewer.layers[0].data == data)

    assert len(viewer.layers) == 1
    assert view.layers.vbox_layout.count() == 2 * len(viewer.layers) + 2

    assert viewer.dims.ndim == 3
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 1

    # Flip dims order displayed
    viewer.dims.order = [0, 2, 1]
    assert viewer.dims.order == [0, 2, 1]

    # Flip dims order including non-displayed
    viewer.dims.order = [1, 0, 2]
    assert viewer.dims.order == [1, 0, 2]

    # Close the viewer
    viewer.window.close()
Exemple #24
0
def test_viewer(qtbot):
    """Test instantiating viewer."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    assert viewer.title == 'napari'
    assert view.viewer == viewer

    assert len(viewer.layers) == 0
    assert view.layers.vbox_layout.count() == 2

    assert viewer.dims.ndim == 2
    assert view.dims.nsliders == viewer.dims.ndim
    assert np.sum(view.dims._displayed_sliders) == 0

    # Switch to 3D rendering mode and back to 2D rendering mode
    viewer.dims.ndisplay = 3
    assert viewer.dims.ndisplay == 3
    viewer.dims.ndisplay = 2
    assert viewer.dims.ndisplay == 2

    # Run all class keybindings
    for func in viewer.class_keymap.values():
        func(viewer)
        # the `play` keybinding calls QtDims.play_dim(), which then creates a new QThread.
        # we must then run the keybinding a second time, which will call QtDims.stop(),
        # otherwise the thread will be killed at the end of the test without cleanup,
        # causing a segmentation fault.  (though the tests still pass)
        if func.__name__ == 'play':
            func(viewer)

    # Close the viewer
    viewer.window.close()
Exemple #25
0
def main():
    with gui_qt():
        viewer = Viewer()
        m = OMEROWidget()
        dw = viewer.window.add_dock_widget(m, area="right")
        # TODO: figure out dynamic geometry
        viewer.window._qt_window.setGeometry(300, 200, 1280, 720)
        viewer.window._qt_window.resizeDocks([dw], [390], Qt.Horizontal)
Exemple #26
0
def test_no_qt_loop():
    """Test informative error raised when no Qt event loop exists.

    Logically, this test should go at the top of the file. Howveer, that
    resulted in tests passing when only this file was run, but failing when
    other tests involving Qt-bot were run before this file. Putting this test
    second provides a sanity check that pytest-ordering is correctly doing its
    magic.
    """
    with pytest.raises(RuntimeError):
        _ = Viewer()
Exemple #27
0
def test_display(qtbot, stack, spots, masks):
    from napari import Viewer

    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    if stack is None and spots is None and masks is None:
        with pytest.raises(TypeError):
            display(stack, spots, masks, viewer=viewer)
    else:
        display(stack, spots, masks, viewer=viewer)
Exemple #28
0
def test_add_layer(qtbot, layer_class, data, ndim, visible):
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)
    layer = add_layer_by_type(viewer, layer_class, data, visible=visible)
    check_viewer_functioning(viewer, view, data, ndim)

    # Run all class keybindings
    for func in layer.class_keymap.values():
        func(layer)

    # Close the viewer
    viewer.window.close()
Exemple #29
0
def test_dask_2D(qtbot):
    """Test adding 2D dask image."""
    viewer = Viewer()
    view = viewer.window.qt_viewer
    qtbot.addWidget(view)

    da.random.seed(0)
    data = da.random.random((10, 15))
    viewer.add_image(data)
    assert np.all(viewer.layers[0].data == data)

    # Close the viewer
    viewer.window.close()
Exemple #30
0
def main_window(qtbot, request):
    if request.param == "remote":
        server.try_kill_server()

    viewer = Viewer(show=False)
    win = MainWindow(viewer=viewer, remote=request.param == "remote")
    config_path = os.path.dirname(os.path.abspath(__file__)) + "/test_config.cfg"
    win._mmc.loadSystemConfiguration(config_path)

    try:
        yield win
    finally:
        viewer.close()