Repository: napari/napari

Issue:
Multiscale 3D rendering warning should happen once only
When I switch to 3D view in a multiscale array, I get the following warning:

```pytb
/Users/jni/projects/napari/napari/layers/image/_slice.py:113: UserWarning: Multiscale rendering is only supported in 2D. In 3D, only the lowest resolution scale is displayed!
```

This is fine, except the warning happens on *every* slice event:

```pytb
...
/Users/jni/projects/napari/napari/layers/image/_slice.py:113: UserWarning: Multiscale rendering is only supported in 2D. In 3D, only the lowest resolution scale is displayed!
/Users/jni/projects/napari/napari/layers/image/_slice.py:113: UserWarning: Multiscale rendering is only supported in 2D. In 3D, only the lowest resolution scale is displayed!
/Users/jni/projects/napari/napari/layers/image/_slice.py:113: UserWarning: Multiscale rendering is only supported in 2D. In 3D, only the lowest resolution scale is displayed!
/Users/jni/projects/napari/napari/layers/image/_slice.py:113: UserWarning: Multiscale rendering is only supported in 2D. In 3D, only the lowest resolution scale is displayed!
...
```

That's a fraction of the console output, and the toast pop-up on the canvas just keeps popping as you interact with the viewer.

@kephale is making good progress to help us get rid of that warning altogether, but in the meantime, a quick fix would be to set a flag for whether we've warned already, and not warn if so.


Discussion:
I was optimistic that this could be fixed with `warnings.simplefilter('once')` and maybe `warnings.catch_warnings`, but that doesn't seem to have effect here. 

I then noticed `filterwarnings` in `pyproject.toml`, which attempts to ignore [all warnings matching this message](https://github.com/napari/napari/blob/c43a2e7c0181b5c4ed877fb1f59cae6f4d0ff0d2/pyproject.toml#L156). I'm not sure if that would have an effect in editable installs, but I could also reproduce this issue on my non-editable v0.4.17 install, so it doesn't seem to have an effect at all - or am I misinterpreting standard warning filtering in Python? I also wonder if napari is handling warnings in a special way that means these get ignored?

We could of course fix this by storing a static boolean that gets set after the first warning, but that feels silly given there are built-in mechanisms for handling this stuff.

I'm going to unassign myself for now and hope someone with more background/knowledge steps in here.
> I then noticed `filterwarnings` in `pyproject.toml`, which attempts to ignore [all warnings matching this message](https://github.com/napari/napari/blob/c43a2e7c0181b5c4ed877fb1f59cae6f4d0ff0d2/pyproject.toml#L156). I'm not sure if that would have an effect in editable installs, but I could also reproduce this issue on my non-editable v0.4.17 install, so it doesn't seem to have an effect at all - or am I misinterpreting standard warning filtering in Python?

From community meeting debugging, I am misinterpreting because that `filterwarnings` only applies to tests. So one mystery solved.
I couldn't resist picking this one back and I think this is actually a Python bug: https://github.com/python/cpython/issues/73858

If I understand correctly, the problem is that on exiting `catch_warnings` forgets about any previously seen warnings, which is why we get repeats even though the `'default'` action is to show once. The tiny minimal reproducing example there shows this off:

```python
import warnings
for _ in range(3):
    with warnings.catch_warnings():
        pass
    warnings.warn('warning')
```

In our case, the troublemaker is (of course...) the thumbnail code which uses `catch_warnings` twice:
- [Image._update_thumbnail](https://github.com/napari/napari/blob/c43a2e7c0181b5c4ed877fb1f59cae6f4d0ff0d2/napari/layers/image/image.py#L927)
- [Image.thumbnail setter](https://github.com/napari/napari/blob/c43a2e7c0181b5c4ed877fb1f59cae6f4d0ff0d2/napari/layers/base/base.py#L872)

To fix this specific issue (i.e. when going through the slicing path), we might be able to remove the first usage (as we require scipy >= 1.4) and we may also be able to remove the second one if we use the `scikit-image` utility and that doesn't issue a warning.

But the general problem is that any other use of `catch_warnings` that gets hit while using napari will cause it to forget, so the issue may get hit again (though likely not on every slice/refresh).
I think this one is back. Serves me right for not writing an explicit test... Here's one that can reproduce the issue:

```python
def test_3d_slice_twice_with_multiscale_image_warns_once():
    data = [np.zeros((4, 4, 4, 4)), np.zeros((2, 2, 2, 2))]
    layer = Image(data, multiscale=True, rgb=False)

    with pytest.warns(
        UserWarning, match="Multiscale rendering is only supported in 2D"
    ):
        layer._slice_dims(point=(0, 0, 0, 0), ndisplay=3)

    layer._slice_dims(point=(1, 0, 0, 0), ndisplay=3)
```

and another that covers more code:

```python
def test_3d_slice_twice_with_multiscale_image_warns_once():
    # Start in 2D to avoid warning when adding the layer.
    viewer = ViewerModel(ndisplay=2)
    data = [np.zeros((4, 4, 4, 4)), np.zeros((2, 2, 2, 2))]
    viewer.add_image(data, multiscale=True, rgb=False)

    # Switch to 3D to trigger the warning.
    with pytest.warns(
        UserWarning, match="Multiscale rendering is only supported in 2D"
    ):
        viewer.dims.ndisplay = 3

    # Simulate moving the slider to slice again, without a warning.
    assert viewer.dims.point[0] != 0
    viewer.dims.point = (0, 0, 0, 0)
```

You'll also have to remove the related ignore line in `pyproject.toml`.

My assumption is that there was a `catch_warnings` added after #5603.

Unless the underlying cpython issue is fixed and backported, it seems like the only fix here is to forbid use of `catch_warnings` in that code path.

This is an issue on `main` and for v0.4.18 unfortunately.

Resolving patch:
diff --git a/napari/layers/image/_slice.py b/napari/layers/image/_slice.py
--- a/napari/layers/image/_slice.py
+++ b/napari/layers/image/_slice.py
@@ -1,4 +1,3 @@
-import warnings
 from dataclasses import dataclass, field
 from typing import Any, Callable, Tuple, Union
 
@@ -9,7 +8,6 @@
 from napari.utils._dask_utils import DaskIndexer
 from napari.utils.misc import reorder_after_dim_reduction
 from napari.utils.transforms import Affine
-from napari.utils.translations import trans
 
 
 @dataclass(frozen=True)
@@ -203,13 +201,6 @@ def _call_single_scale(self) -> _ImageSliceResponse:
 
     def _call_multi_scale(self) -> _ImageSliceResponse:
         if self.dims.ndisplay == 3:
-            warnings.warn(
-                trans._(
-                    'Multiscale rendering is only supported in 2D. In 3D, only the lowest resolution scale is displayed',
-                    deferred=True,
-                ),
-                category=UserWarning,
-            )
             level = len(self.data) - 1
         else:
             level = self.data_level


Test patch: