Repository: napari/napari

Issue:
ValidationError when trying to plot more than 16 different colors
### 🐛 Bug Report

Dear napari team,

when I try to plot points with more than 16 different colors I get following error:

```python

File [AppData\Local\miniconda3\envs\xparse\lib\site-packages\napari\utils\events\evented_model.py:242](/AppData/Local/miniconda3/envs/xparse/lib/site-packages/napari/utils/events/evented_model.py:242), in EventedModel.__init__(self, **kwargs)
    [241] -> None:
--> [242]     super().__init__(**kwargs)
    [244]     self._events.source = self
    [245]     # add event emitters for each field which is mutable

File [AppData\Local\miniconda3\envs\xparse\lib\site-packages\pydantic\main.py:341](/Local/miniconda3/envs/xparse/lib/site-packages/pydantic/main.py:341), in pydantic.main.BaseModel.__init__()

ValidationError: 1 validation error for ColorManager
__root__
  Cannot cast array data from dtype('O') to dtype('float64') according to the rule 'safe' (type=type_error)
``` 

For 16 different colors or less it is no problem.


### 💡 Steps to Reproduce

The bug can be reproduced using following code:

```python
from skimage import data
import numpy as np
import napari

# set parameters for point generation
n_points = 100
n_clusters = 17
points = np.random.rand(n_points, 2) * 100

# start viewer
viewer = napari.view_image(data.astronaut(), rgb=True)

# set point properties
point_properties = {
    'abc': np.random.choice([str(elem) for elem in np.arange(n_clusters)], n_points)
}

# add points
points_layer = viewer.add_points(
    points,
    properties=point_properties,
    face_color='abc',
    face_color_cycle=['magenta', 'green'],
    edge_width=0.1,
)
``` 

The number of clusters can be changed with `n_clusters`.

### 💡 Expected Behavior

I expect to be able to plot points with more than 16 different colors. Since I only offered a color cycle with two colors, I expect napari to behave the same for >16 points than <=16 points.

### 🌎 Environment

napari: 0.4.18
Platform: Windows-10-10.0.19045-SP0
Python: 3.9.18 (main, Sep 11 2023, 14:09:26) [MSC v.1916 64 bit (AMD64)]
Qt: 5.15.2
PyQt5: 5.15.9
NumPy: 1.25.2
SciPy: 1.9.3
Dask: 2023.9.2
VisPy: 0.12.2
magicgui: 0.7.3
superqt: 0.6.0
in-n-out: 0.1.8
app-model: 0.2.2
npe2: 0.7.2

OpenGL:
  - GL version:  4.6.0 Compatibility Profile Context 23.10.24.05.230830
  - MAX_TEXTURE_SIZE: 16384

Screens:
  - screen 1: resolution 2560x1600, scale 1.0
  - screen 2: resolution 3840x2160, scale 1.0
  - screen 3: resolution 2880x1800, scale 1.0

Settings path:
  - [AppData\Local\napari\xparse_57fe1a37b30a9e37de3a06866d324c7f56a92d1a\settings.yaml](/AppData/Local/napari/xparse_57fe1a37b30a9e37de3a06866d324c7f56a92d1a/settings.yaml)
Plugins:
  - napari: 0.4.18 (77 contributions)
  - napari-console: 0.0.8 (0 contributions)
  - napari-svg: 0.1.10 (2 contributions)
  - ome-types: 0.4.2 (2 contributions)

### 💡 Additional Context

Also it was really difficult to detect this error. I encountered the problem in a function that was included into a dock widget. And when I tried to add the points via the widget, simply nothing happened. No error message appeared. Is it possible to somehow show the error messages when executing functions from widgets?

Beside that it is really great to work with napari! Thanks a lot in advance!


Discussion:
FYI I updated your sample code to include imports so it can be copy-pasted into REPL.
I can reproduce on main.
If you change:
`n_clusters = 17`
to 15 no error occurs.

Thanks for updating that! Forgot to include it.

Yes that's what I observe. For `n_clusters`<=16, it doesn't give an error. My question is why it gives an error for `n_clusters` > 16. Do you have an idea?
Nope, I'd sort of expect an error whenever your property to color by has more flavors than the number of colors passed...
Yes this is what I would also expect. But in this case the number of colors is only 2 and napari just cycles through them. Which I think is a good featureand better than not allowing this case at all. A little warning message that the number of colors required is higher than the number of colors passed would be nice, though. But this is not the explanation for the error that I see above 16 colors. Here for example I tested it with 18 different colors and it gives still the same error:

```python
import napari
from skimage import data
import numpy as np

# set parameters for point generation
n_points = 100
n_clusters = 17
color_cycle = ['#1f77b4',
                '#ff7f0e',
                '#2ca02c',
                '#d62728',
                '#9467bd',
                '#8c564b',
                '#e377c2',
                '#bcbd22',
                '#17becf',
                '#aec7e8',
                '#ffbb78',
                '#98df8a',
                '#ff9896',
                '#c5b0d5',
                '#c49c94',
                '#f7b6d2',
                '#dbdb8d',
                '#9edae5'
            ]

points = np.random.rand(n_points, 2) * 100

# start viewer
viewer = napari.view_image(data.astronaut(), rgb=True)

# set point properties
point_properties = {
    'abc': np.random.choice([str(elem) for elem in np.arange(n_clusters)], n_points),
    #'abc': np.random.choice([elem for elem in np.arange(n_clusters)], n_points)
}

# add points
points_layer = viewer.add_points(
    points,
    properties=point_properties,
    face_color='abc',
    face_color_cycle=color_cycle,
    edge_width=0.1,
)
```
Are there any ideas how to solve this?
Ok. The source of this problem is here:

https://github.com/napari/napari/blob/c9c398187094887a10aa9d2544c1816d7baaf509/napari/layers/utils/color_manager_utils.py#L25-L28

You could see magical number 16 here. 

The simple workaround is to change adding point to this:

```python
points_layer = viewer.add_points(
    points,
    properties=point_properties,
    face_color={"color_mode": "cycle", "colors": "abc"},
    face_color_cycle=['magenta', 'green'],
    edge_width=0.1,
)
```

Resolving patch:
diff --git a/napari/layers/utils/color_manager_utils.py b/napari/layers/utils/color_manager_utils.py
--- a/napari/layers/utils/color_manager_utils.py
+++ b/napari/layers/utils/color_manager_utils.py
@@ -22,9 +22,9 @@ def guess_continuous(color_map: np.ndarray) -> bool:
         True of the property is guessed to be continuous, False if not.
     """
     # if the property is a floating type, guess continuous
-    return (
-        issubclass(color_map.dtype.type, np.floating)
-        or len(np.unique(color_map)) > 16
+    return issubclass(color_map.dtype.type, np.floating) or (
+        len(np.unique(color_map)) > 16
+        and isinstance(color_map.dtype.type, np.integer)
     )
 
 


Test patch: