Repository: napari/napari

Issue:
Shuffle in labels layer broken in current master
## 🐛 Bug

In the current master, clicking shuffle in the labels layer has no effect.


## To Reproduce

Steps to reproduce the behavior:

1. Open the viewer with a label layer. E.g. run `examples/labels-2d.py`
2. Select the labels layer
3. Click the suffle button

## Expected behavior

Expected behaviour: label colours should change.

## Environment
napari: 0.4.8.dev22+g8d77f01d
Platform: Linux-5.8.0-48-generic-x86_64-with-debian-bullseye-sid
System: Ubuntu 20.04.2 LTS
Python: 3.7.9 (default, Aug 31 2020, 12:42:55) [GCC 7.3.0]
Qt: 5.15.2
PyQt5: 5.15.2
NumPy: 1.19.5
SciPy: 1.6.0
Dask: 2021.01.1
VisPy: 0.6.6

OpenGL:
- GL version: 4.6.0 NVIDIA 460.39
- MAX_TEXTURE_SIZE: 32768

Screens:
- screen 1: resolution 1920x1200, scale 1.0

Plugins:
- affinder: 0.1.dev13+g89050b4
- console: 0.0.2
- napping: 0.1.2
- scikit-image: 0.4.8.dev22+g8d77f01d
- svg: 0.1.4



Discussion:
Just tested this in a fresh environment. This bug is not just on current master, it is also present in the 0.4.7 release. It is not present in 0.4.6 release.
Ok, this bug was introduced in the following commit @DragaDoncila 
https://github.com/napari/napari/commit/f0e108ed4d0dda0317b361c7e76923931268a73b
Thanks for filing @VolkerH! Maybe @DragaDoncila can check this out when she gets the chance
I tracked it down to here: 
https://github.com/napari/napari/blob/8d77f01da0da680344eed6b9e02c8e44cb032326/napari/layers/labels/labels.py#L716-L724

If I disable the code in the `try` block (which I don't fully understand) and instead run the code in the `except IndexError` block the shuffling is restored.

Ok, so there is no check whether the seed has changed. I think what needs to happen is that the setter for `seed` needs to somehow invalidate `self._all_vals`.
The try is seeing if all the labels have currently been mapped to colors. We should add some comments there too. When doing the shuffle (i.e. changing the seed) we need to remake the `_all_vals` map

If you add

```python
     max_val = np.max(raw) 
     self._all_vals = low_discrepancy_image( 
         np.arange(max_val + 1), self._seed 
     ) 
     self._all_vals[0] = 0 
```

to the seed setter here then it should work

https://github.com/napari/napari/blob/8d77f01da0da680344eed6b9e02c8e44cb032326/napari/layers/labels/labels.py#L315

It's a little awkward with how to best get the max value - which is a limit of this approach. Curious what @jni or @DragaDoncila would recommend here too
> Ok, so there is no check whether the seed has changed. I think what needs to happen is that the setter for seed needs to somehow invalidate self._all_vals.

Yup exactly! Beat me too it!!
if  I just add 
```
self._all_vals = None
```
to the seed setter, the shuffle works as that causes the IndexError and therefore triggers the except block which restores a correct `_all_vals`. However, I am not deep enough into the code to oversee the potential side effects of that approach. It is probably bad style to force an Error to trigger an update (OTOH it doesn't require the duplication of the code block you quoted above)
ooo, very nice @VolkerH - I like that, i'm ok with `None` and re-triggering an update. Can you open a PR and then the others can review! Thanks for all this debugging
will do, give me a minute


Resolving patch:
diff --git a/napari/layers/labels/labels.py b/napari/layers/labels/labels.py
--- a/napari/layers/labels/labels.py
+++ b/napari/layers/labels/labels.py
@@ -314,6 +314,9 @@ def seed(self):
     def seed(self, seed):
         self._seed = seed
         self._selected_color = self.get_color(self.selected_label)
+        # invalidate _all_vals to trigger re-generation
+        # in _raw_to_displayed
+        self._all_vals = np.array([])
         self.refresh()
         self.events.selected_label()
 


Test patch:
diff --git a/napari/layers/labels/_tests/test_labels.py b/napari/layers/labels/_tests/test_labels.py
--- a/napari/layers/labels/_tests/test_labels.py
+++ b/napari/layers/labels/_tests/test_labels.py
@@ -4,6 +4,7 @@
 import pytest
 import xarray as xr
 from numpy.core.numerictypes import issubdtype
+from numpy.testing import assert_array_almost_equal, assert_raises
 from skimage import data
 
 from napari._tests.utils import check_layer_world_data_extent
@@ -230,6 +231,15 @@ def test_seed():
     layer = Labels(data, seed=0.7)
     assert layer.seed == 0.7
 
+    # ensure setting seed triggers
+    # recalculation of _all_vals
+    _all_vals_07 = layer._all_vals.copy()
+    layer.seed = 0.4
+    _all_vals_04 = layer._all_vals.copy()
+    assert_raises(
+        AssertionError, assert_array_almost_equal, _all_vals_04, _all_vals_07
+    )
+
 
 def test_num_colors():
     """Test setting number of colors in colormap."""