Repository: napari/napari

Issue:
Use vispy volume layer only for rendering
# Description
This PR would supersede #1402, #1403, #1056 and close #1401, close #1399, and close #1398 when done.

The main change is that it now exclusively uses the VisPy `Volume` visual for all our rendoring both `2D` and `3D`. One can see how it dramatically simplifies our `vispy_image_layer.py` file and will make long term maintenance much easier for us.

It also incorporates the changes in https://github.com/vispy/vispy/pull/1842 as a vendored file `base_volume_visual.py` (plus one line `uniform float u_attenuation;`. The rest of the changes to the Volume layer are kept in the `volume.py` file and are fairly minimal, though ultimately ones we'd want to propagate back to VisPy too.

I still need to chase down some corner cases, and we should probably add support for rgb rendering of volumes (see https://github.com/vispy/vispy/issues/1256). We will also have to restrict our interpolation modes to only those available on 3D images which are `nearest` and `linear`.

If @tlambert03 and @jni like this direction I can work towards cleaning it all up. Looking forward to initial feedback!!



Discussion:
I think i'd want to know that it actually fixes something like #1347 before weighing in.  On the surface, I don't really love the idea.  but if it can be demonstrated that it solves bugs that we have been struggling with, then it's more compelling
@tlambert03 I do get this warning
```python-traceback
(base) czirwc1macos2701:napari nsofroniew$ python examples/add_points.py
/Users/nsofroniew/GitHub/napari/napari/_vispy/base_volume_visual.py:648: RuntimeWarning: invalid value encountered in double_scalars
  clim0 = (clim0 - range_min) / tex_range
/Users/nsofroniew/GitHub/napari/napari/_vispy/base_volume_visual.py:649: RuntimeWarning: invalid value encountered in double_scalars
  clim1 = (clim1 - range_min) / tex_range
```

When running `examples/add_image.py` (or really any example), not quite sure why yet, but maybe something to look into before https://github.com/vispy/vispy/pull/1842 merges
> I think i'd want to know that it actually fixes something like #1347 before weighing in. On the surface, I don't really love the idea. but if it can be demonstrated that it solves bugs that we have been struggling with, then it's more compelling

fair enough, I can work to get all our current tests passing and then see how it affects both CI and some dedicated windows testing. Shouldn't be too hard.

I'm honestly not sure if it will or will not fix the nasty windows bug, but I do find the change overall a simplification. Are you worried about performance differences between how the `Image` and `Volume` node actually interact with the `OpenGL` and that the padding approach might lead to performance decrements for the 2D viewing case? I havn't looked into that too much yet, so curious if you have intuition there. Or is there something else to be concerned about that I'm just completely missing?
As there has been some concern about whether this PR would impact performance I have run our existing `QtViewerImageSuite` [benchmarking suite](https://github.com/napari/napari/blob/master/napari/benchmarks/benchmark_qt_viewer_image.py#L48-L81) on master and this PR.

The results are as follows:

## Master -  QtViewerImageSuite

```
(base) czirwc1macos2701:napari nsofroniew$ asv run -b QtViewerImageSuite --python=same
· Discovering benchmarks
· Running 5 total benchmarks (1 commits * 1 environments * 5 benchmarks)
[  0.00%] ·· Benchmarking existing-py_Users_nsofroniew_opt_anaconda3_bin_python
[ 10.00%] ··· Running (benchmark_qt_viewer_image.QtViewerImageSuite.time_get_value--).
[ 20.00%] ··· Running (benchmark_qt_viewer_image.QtViewerImageSuite.time_refresh--).
[ 30.00%] ··· Running (benchmark_qt_viewer_image.QtViewerImageSuite.time_set_view_slice--).
[ 40.00%] ··· Running (benchmark_qt_viewer_image.QtViewerImageSuite.time_update_thumbnail--).
[ 50.00%] ··· Running (benchmark_qt_viewer_image.QtViewerImageSuite.time_zoom--).
[ 60.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_get_value                                                                                                                                 ok
[ 60.00%] ··· ======== =============
               param1
              -------- -------------
                 16      13.8±0.4μs
                 32      14.1±0.1μs
                 64     13.7±0.06μs
                128      13.2±0.2μs
                256      13.4±0.2μs
                512      13.7±0.3μs
                1024     13.6±0.3μs
                2048     13.7±0.2μs
                4096     14.1±0.3μs
              ======== =============

[ 70.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_refresh                                                                                                                                   ok
[ 70.00%] ··· ======== =============
               param1
              -------- -------------
                 16     3.52±0.09ms
                 32      3.37±0.1ms
                 64      3.50±0.1ms
                128     3.48±0.08ms
                256     3.50±0.07ms
                512      3.88±0.1ms
                1024     4.19±0.2ms
                2048     7.78±0.4ms
                4096     25.2±0.4ms
              ======== =============

[ 80.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_set_view_slice                                                                                                                            ok
[ 80.00%] ··· ======== ==========
               param1
              -------- ----------
                 16     77.7±2μs
                 32     76.2±3μs
                 64     77.7±1μs
                128     78.5±3μs
                256     77.5±1μs
                512     76.9±1μs
                1024    77.7±1μs
                2048    77.0±1μs
                4096    78.8±3μs
              ======== ==========

[ 90.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_update_thumbnail                                                                                                                          ok
[ 90.00%] ··· ======== =============
               param1
              -------- -------------
                 16       969±7μs
                 32       971±10μs
                 64     1.01±0.03ms
                128     1.03±0.03ms
                256      1000±20μs
                512       994±10μs
                1024      988±20μs
                2048      976±6μs
                4096      978±30μs
              ======== =============

[100.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_zoom                                                                                                                                      ok
[100.00%] ··· ======== =============
               param1
              -------- -------------
                 16     1.33±0.02ms
                 32     1.37±0.03ms
                 64     1.35±0.01ms
                128     1.42±0.04ms
                256     1.37±0.05ms
                512     1.34±0.03ms
                1024    1.35±0.02ms
                2048    1.38±0.05ms
                4096    1.34±0.01ms
              ======== =============
```

## 1404 -  QtViewerImageSuite

```
(base) czirwc1macos2701:napari nsofroniew$ asv run -b QtViewerImageSuite --python=same
· Discovering benchmarks
· Running 5 total benchmarks (1 commits * 1 environments * 5 benchmarks)
[  0.00%] ·· Benchmarking existing-py_Users_nsofroniew_opt_anaconda3_bin_python
[ 10.00%] ··· Running (benchmark_qt_viewer_image.QtViewerImageSuite.time_get_value--).
[ 20.00%] ··· Running (benchmark_qt_viewer_image.QtViewerImageSuite.time_refresh--).
[ 30.00%] ··· Running (benchmark_qt_viewer_image.QtViewerImageSuite.time_set_view_slice--).
[ 40.00%] ··· Running (benchmark_qt_viewer_image.QtViewerImageSuite.time_update_thumbnail--).
[ 50.00%] ··· Running (benchmark_qt_viewer_image.QtViewerImageSuite.time_zoom--).
[ 60.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_get_value                                                                                                                                 ok
[ 60.00%] ··· ======== ============
               param1
              -------- ------------
                 16     16.7±0.2μs
                 32     16.9±0.6μs
                 64     17.5±0.8μs
                128      17.3±1μs
                256     17.0±0.8μs
                512     16.4±0.2μs
                1024    16.3±0.1μs
                2048    16.4±0.2μs
                4096    16.4±0.5μs
              ======== ============

[ 70.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_refresh                                                                                                                                   ok
[ 70.00%] ··· ======== =============
               param1
              -------- -------------
                 16     1.49±0.03ms
                 32     1.50±0.01ms
                 64     1.53±0.02ms
                128     1.58±0.06ms
                256     1.67±0.01ms
                512     2.29±0.04ms
                1024     4.73±0.3ms
                2048     15.4±0.4ms
                4096      63.1±3ms
              ======== =============

[ 80.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_set_view_slice                                                                                                                            ok
[ 80.00%] ··· ======== ==========
               param1
              -------- ----------
                 16     78.5±2μs
                 32     78.0±1μs
                 64     77.9±1μs
                128     77.8±1μs
                256     80.8±2μs
                512     79.4±2μs
                1024    78.2±4μs
                2048    78.4±5μs
                4096    78.0±1μs
              ======== ==========

[ 90.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_update_thumbnail                                                                                                                          ok
[ 90.00%] ··· ======== =============
               param1
              -------- -------------
                 16       978±20μs
                 32       988±20μs
                 64       985±20μs
                128       983±30μs
                256       981±20μs
                512     1.00±0.02ms
                1024      973±5μs
                2048    1.03±0.04ms
                4096      998±30μs
              ======== =============

[100.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_zoom                                                                                                                                      ok
[100.00%] ··· ======== =============
               param1
              -------- -------------
                 16     1.43±0.03ms
                 32     1.39±0.04ms
                 64     1.41±0.03ms
                128     1.40±0.02ms
                256     1.38±0.01ms
                512     1.40±0.01ms
                1024    1.40±0.05ms
                2048     1.42±0.1ms
                4096    1.40±0.02ms
              ======== =============
```

It's quite interesting. Panning/ zooming is the same for both approaches. The refresh is faster on this for small images, possibly because the clims operation has been moved to shader, but for large images this approach is slower right now. A refresh of a `4096 x 4096` image with this approach is `63.1ms` vs `25.2ms` with the current approach.

I'm going to look into this more, I'm going to run some more of the benchmarks too and see if I can figure out some more info. There could also be some improvements to the `VolumeVisual` that might speed things up in the 2D case. I really do think just having the one visual is preferable if possible!!

More coming soon!!
Just some more data from another benchmark, [QtViewerSingleImageSuite](https://github.com/napari/napari/blob/master/napari/benchmarks/benchmark_qt_viewer_image.py#L84-L124), looking at some more function calls.

Here the underling data was only `128x128x128` rendered either in "2D" or "3D" so there aren't really any striking differences. It does seem like the bigger differences come for larger images.

## Master - QtViewerSingleImageSuite

```
(base) czirwc1macos2701:napari nsofroniew$ asv run -b QtViewerSingleImageSuite --python=same
· Discovering benchmarks
· Running 7 total benchmarks (1 commits * 1 environments * 7 benchmarks)
[  0.00%] ·· Benchmarking existing-py_Users_nsofroniew_opt_anaconda3_bin_python
[  7.14%] ··· Running (benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_get_value--).......
[ 57.14%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_get_value                                                                                                                   13.7±0.2μs
[ 64.29%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_ndisplay                                                                                                                      966±40ns
[ 71.43%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_refresh                                                                                                                    3.60±0.07ms
[ 78.57%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_set_data                                                                                                                   5.79±0.07ms
[ 85.71%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_set_view_slice                                                                                                                 124±3μs
[ 92.86%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_update_thumbnail                                                                                                               972±8μs
[100.00%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_zoom                                                                                                                       1.35±0.02ms
```

## 1404 - QtViewerSingleImageSuite

```
(base) czirwc1macos2701:napari nsofroniew$ asv run -b QtViewerSingleImageSuite --python=same
· Discovering benchmarks
· Running 7 total benchmarks (1 commits * 1 environments * 7 benchmarks)
[  0.00%] ·· Benchmarking existing-py_Users_nsofroniew_opt_anaconda3_bin_python
[  7.14%] ··· Running (benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_get_value--).......
[ 57.14%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_get_value                                                                                                                   16.0±0.2μs
[ 64.29%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_ndisplay                                                                                                                       760±3ns
[ 71.43%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_refresh                                                                                                                    1.63±0.01ms
[ 78.57%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_set_data                                                                                                                   3.62±0.05ms
[ 85.71%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_set_view_slice                                                                                                                 124±2μs
[ 92.86%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_update_thumbnail                                                                                                              960±10μs
[100.00%] ··· benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_zoom                                                                                                                       1.37±0.01ms
```
Here are the results of another benchmark [QtViewerAddImageSuite](https://github.com/napari/napari/blob/master/napari/benchmarks/benchmark_qt_viewer_image.py#L29-L45). This one again looks at images of different sizes and the data differences become more pronounced for larger data again.

## Master - QtViewerAddImageSuite

```
(base) czirwc1macos2701:napari nsofroniew$ asv run -b QtViewerAddImageSuite --python=same
· Discovering benchmarks
· Running 1 total benchmarks (1 commits * 1 environments * 1 benchmarks)
[  0.00%] ·· Benchmarking existing-py_Users_nsofroniew_opt_anaconda3_bin_python
[ 50.00%] ··· Running (benchmark_qt_viewer_image.QtViewerAddImageSuite.time_add_image--).
[100.00%] ··· benchmark_qt_viewer_image.QtViewerAddImageSuite.time_add_image                                                                                                                              ok
[100.00%] ··· ======== ============
               param1
              -------- ------------
                 16      42.3±1ms
                 32     42.0±0.7ms
                 64     42.5±0.7ms
                128     42.0±0.5ms
                256     42.5±0.2ms
                512     43.8±0.6ms
                1024     48.3±1ms
                2048     66.2±4ms
                4096     163±8ms
              ======== ============
```

## 1404 - QtViewerAddImageSuite

```
(base) czirwc1macos2701:napari nsofroniew$ asv run -b QtViewerAddImageSuite --python=same
· Discovering benchmarks
· Running 1 total benchmarks (1 commits * 1 environments * 1 benchmarks)
[  0.00%] ·· Benchmarking existing-py_Users_nsofroniew_opt_anaconda3_bin_python
[ 50.00%] ··· Running (benchmark_qt_viewer_image.QtViewerAddImageSuite.time_add_image--).
[100.00%] ··· benchmark_qt_viewer_image.QtViewerAddImageSuite.time_add_image                                                                                                                              ok
[100.00%] ··· ======== ============
               param1
              -------- ------------
                 16     37.4±0.3ms
                 32      37.8±1ms
                 64     37.4±0.5ms
                128     37.5±0.4ms
                256     38.3±0.4ms
                512     40.4±0.3ms
                1024    50.7±0.2ms
                2048    91.0±0.6ms
                4096     282±10ms
              ======== ============
```
Doing profiling of the `4096x4096` refresh with the following yields

```
asv profile "benchmark_qt_viewer_image.QtViewerImageSuite.time_refresh\(4096\)" -g snakeviz --python=same
```

## Master - time_refresh

![image](https://user-images.githubusercontent.com/6531703/86555778-3fd73980-bf06-11ea-86b0-53f0a8aa36a2.png)

## 1404 - time_refresh

![image](https://user-images.githubusercontent.com/6531703/86555685-ea029180-bf05-11ea-8d39-732fcd11639f.png)

The key vispy `set_data` call goes from `~3ms` to `~25ms`, with `~18ms` taken by another numpy call, which may be duplicative from the one we are already inside our own `_on_slice_data_change`. We definitely don't want to make both these numpy calls (ideal would be if didn't have to make either), but I think if we can get this down to just one of them then we'll be in much better shape.

I do recall many convos a while back out the volume layer doing "an extra copy". I'm not sure if that's still happening now or if we could remove it, especially if we're doing this numpy type conversion inside `_on_slice_data_change`.

I'd welcome more input from others who've looked into this stuff too at this point. I still think this might be a promising approach!!
great data.  The main differences I can remember between the `Image` and `Volume` visuals is that (obviously) the Image uses a 2D texture on the GPU and the Volume uses a 3D texture.  Each is optimized for different spatial locality of the data, so we should read up on what we might be compromising in the long term by representing 2D information as a 3D GPU texture.  (I don't immediately know the answer, but I can imagine there are differences that would matter.  here is a [potentially interesting thread](https://community.khronos.org/t/2d-texture-array-vs-3d-texture/73003))

> I do recall many convos a while back out the volume layer doing "an extra copy"

Yep, everytime you change the data in a Volume by calling `set_data()`, it [creates a copy of that array](https://github.com/vispy/vispy/blob/master/vispy/visuals/volume.py#L487) while building the new 3D texture.  For the Image visual, the corresponding texture-preparation computations are in [`_build_texture`](https://github.com/vispy/vispy/blob/caed4f0326dc9c0804b254339c4d89f66e6a01b6/vispy/visuals/image.py#L466) ... and for many `dtype`s there, it doesn't need to duplicate the data.

This is not to say that conceptually, it wouldn't be possible to optimize the preparation of a 3D texture to be used as proposed here (for 2D data).  But that's the part that I am specifically not so sure about, and I think it would require a lot of tinkering to be confident (and likely a fair amount of refactoring)
That article is pretty intense on the differences!! I do wonder though if even for 4096x4096 and bigger images we're still in a regime with 2D images where the performance hit once we get to the gpu will be low, it's just so much simpler then the crazy 3D game stuff, but we'll see.

If we only have the VolumeVisual now which always has the line
```python
        vol = np.array(vol, dtype='float32', copy=copy)
```
then do we still need
```python
        dtype = np.dtype(data.dtype)
        if dtype not in texture_dtypes:
            try:
                dtype = dict(
                    i=np.int16, f=np.float32, u=np.uint16, b=np.uint8
                )[dtype.kind]
            except KeyError:  # not an int or float
                raise TypeError(
                    f'type {dtype} not allowed for texture; must be one of {set(texture_dtypes)}'  # noqa: E501
                )
            data = data.astype(dtype)
```
within our code? This definitely feels redundant now, but I know we added it because there were problems before!!!
With no extra dtype setting things get better for the #1404 approach, but that copy call is still slowing us down somewhat.

## 1404 no extra dtype setting

```
[ 70.00%] ··· benchmark_qt_viewer_image.QtViewerImageSuite.time_refresh                                                                                                                                   ok
[ 70.00%] ··· ======== =============
               param1
              -------- -------------
                 16     1.49±0.02ms
                 32     1.54±0.05ms
                 64     1.50±0.02ms
                128     1.56±0.01ms
                256     1.68±0.03ms
                512     2.18±0.02ms
                1024     4.30±0.1ms
                2048     12.7±0.2ms
                4096      47.9±1ms
              ======== =============
```
I've cleaned up this PR a tiny bit and skipped the failing tests (right now for multiscale and a dims rolling test) just to see if this change makes our windows CI pass!! Will be interesting to see if it's the case. It might also be interesting to see if this fixes #1347 too, but I need help from people with windows machines there. Note also that the PR is still a draft and there are things I know are broken right now, but I think enough is working so that we should be able to determine if the windows specific problems are fixed!!
# [Codecov](https://codecov.io/gh/napari/napari/pull/1404?src=pr&el=h1) Report
> Merging [#1404](https://codecov.io/gh/napari/napari/pull/1404?src=pr&el=desc) into [master](https://codecov.io/gh/napari/napari/commit/9ea724b8662a7a2948b8400363e0c9bd0d1f267e&el=desc) will **decrease** coverage by `0.20%`.
> The diff coverage is `86.57%`.

[![Impacted file tree graph](https://codecov.io/gh/napari/napari/pull/1404/graphs/tree.svg?width=650&height=150&src=pr&token=1LfdDeezHt)](https://codecov.io/gh/napari/napari/pull/1404?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1404      +/-   ##
==========================================
- Coverage   87.55%   87.35%   -0.21%     
==========================================
  Files         240      241       +1     
  Lines       21629    22246     +617     
==========================================
+ Hits        18937    19432     +495     
- Misses       2692     2814     +122     
```


| [Impacted Files](https://codecov.io/gh/napari/napari/pull/1404?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [napari/\_vispy/base\_volume\_visual.py](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree#diff-bmFwYXJpL192aXNweS9iYXNlX3ZvbHVtZV92aXN1YWwucHk=) | `81.86% <81.86%> (ø)` | |
| [napari/\_vispy/vispy\_image\_layer.py](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree#diff-bmFwYXJpL192aXNweS92aXNweV9pbWFnZV9sYXllci5weQ==) | `94.11% <93.75%> (+5.82%)` | :arrow_up: |
| [napari/\_qt/layers/qt\_image\_layer.py](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree#diff-bmFwYXJpL19xdC9sYXllcnMvcXRfaW1hZ2VfbGF5ZXIucHk=) | `100.00% <100.00%> (ø)` | |
| [napari/\_tests/test\_viewer.py](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree#diff-bmFwYXJpL190ZXN0cy90ZXN0X3ZpZXdlci5weQ==) | `80.68% <100.00%> (-12.18%)` | :arrow_down: |
| [napari/\_vispy/\_tests/test\_image\_rendering.py](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree#diff-bmFwYXJpL192aXNweS9fdGVzdHMvdGVzdF9pbWFnZV9yZW5kZXJpbmcucHk=) | `100.00% <100.00%> (ø)` | |
| [napari/\_vispy/\_tests/test\_vispy\_multiscale.py](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree#diff-bmFwYXJpL192aXNweS9fdGVzdHMvdGVzdF92aXNweV9tdWx0aXNjYWxlLnB5) | `19.76% <100.00%> (-80.24%)` | :arrow_down: |
| [napari/\_vispy/volume.py](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree#diff-bmFwYXJpL192aXNweS92b2x1bWUucHk=) | `100.00% <100.00%> (+21.15%)` | :arrow_up: |
| [napari/layers/image/\_tests/test\_image.py](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree#diff-bmFwYXJpL2xheWVycy9pbWFnZS9fdGVzdHMvdGVzdF9pbWFnZS5weQ==) | `100.00% <100.00%> (ø)` | |
| [napari/layers/image/\_tests/test\_multiscale.py](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree#diff-bmFwYXJpL2xheWVycy9pbWFnZS9fdGVzdHMvdGVzdF9tdWx0aXNjYWxlLnB5) | `100.00% <100.00%> (ø)` | |
| [napari/layers/image/image.py](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree#diff-bmFwYXJpL2xheWVycy9pbWFnZS9pbWFnZS5weQ==) | `98.76% <100.00%> (+0.52%)` | :arrow_up: |
| ... and [92 more](https://codecov.io/gh/napari/napari/pull/1404/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/napari/napari/pull/1404?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/napari/napari/pull/1404?src=pr&el=footer). Last update [9ea724b...61d9903](https://codecov.io/gh/napari/napari/pull/1404?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

Windows test passed!!! It could be coincidence, the skipped tests, or it could be our fix!!!! Would be great if we could get someone who's been struggling with #1347 to try out this PR and see if they still get the same errors. @Czaki @VolkerH @ziyangczi @tlambert03.

Even if we don't go with this exact implementation as the solution, would still be good to know if this clears up our windows specific vispy problems!!!
![obraz](https://user-images.githubusercontent.com/3826210/86644470-d32f6e00-bfdd-11ea-8a20-b496f2c89ce2.png)

It does not crash, but it dose not render properly because it ignore scalling in z

![obraz](https://user-images.githubusercontent.com/3826210/86644725-12f65580-bfde-11ea-88a4-782e696b6af4.png)

I have hope that after fix scaling it will still work.
 
> It does not crash, but it dose not render properly because it ignore scalling in z

This is amazing @Czaki. Very exciting. I think it means that the creation and destroying of the `Image` / `Volume` visuals was  causing the windows crashes!!! (The no z-scaling is easy to fix, just one of the things not yet finished in this PR)

Stepping back I now see three options for us

1. Fix the create/ destroy so that all objects are properly deleted / remade and continue with the swapping `Image` / `Volume` approach.

2. Create a compound visual that contains both `Image` and `Volume`, apply things like colormaps etc to both of them, but raw data only to one at a time and toggle on / off the visibility of the not used one (I'm not sure this will actually work because of what the camera expects, but we could see).

3. Only use the volume layer with either major or minor modifications to ensure that 2D image rendering is as performant as before.

I'm most excited about option 3 (which would be this PR) but curious what others think. We'd definitely need to make sure performance and functionality don't suffer.
@jni asked me via Signal to test this PR (thanks, I  missed the mention here)

I installed it in the same environment and ran the same script as in this post https://github.com/napari/napari/issues/1347#issuecomment-645762488 .

This time it didn't crash.

Edited to add:
Can also confirm that I can now render the volume that caused the crash in the first post of the issue (https://github.com/napari/napari/issues/1347#issue-635033626).

Resolving patch:
diff --git a/napari/_vispy/vispy_image_layer.py b/napari/_vispy/vispy_image_layer.py
--- a/napari/_vispy/vispy_image_layer.py
+++ b/napari/_vispy/vispy_image_layer.py
@@ -18,8 +18,9 @@
 
 class VispyImageLayer(VispyBaseLayer):
     def __init__(self, layer):
-        node = ImageNode(None, method='auto')
-        super().__init__(layer, node)
+        self._image_node = ImageNode(None, method='auto')
+        self._volume_node = VolumeNode(np.zeros((1, 1, 1)), clim=[0, 1])
+        super().__init__(layer, self._image_node)
 
         self.layer.events.rendering.connect(self._on_rendering_change)
         self.layer.events.interpolation.connect(self._on_interpolation_change)
@@ -39,11 +40,13 @@ def _on_display_change(self, data=None):
         self.node.parent = None
 
         if self.layer.dims.ndisplay == 2:
-            self.node = ImageNode(data, method='auto')
+            self._image_node.set_data(data)
+            self.node = self._image_node
         else:
             if data is None:
                 data = np.zeros((1, 1, 1))
-            self.node = VolumeNode(data, clim=self.layer.contrast_limits)
+            self._volume_node.set_data(data, clim=self.layer.contrast_limits)
+            self.node = self._volume_node
 
         self.node.parent = parent
         self.reset()


Test patch: