Repository: napari/napari

Issue:
Adding more than 1024 points causes the 3D viewer to crash.
## 🐛 Bug

Napari crashes if I add more than 1024 3D points and switch to the 3D view. It also crashes with more than 1024 points if I explicit start up Napari with the 3D view.

## To Reproduce

```
import numpy as np
import napari

with napari.gui_qt():
	myViewer = napari.Viewer()

	# works up to 1024 points but crashes if >1024 points
	s = (1025,3)
	# (1025,3) random integers in the range [10, 100)
	myPoints = np.random.randint(10,100,s)
	pointLayer = myViewer.add_points(myPoints)
```
1. Run the code above
2. Click the 3D view button/icon
3. Napari crashes

## Environment

MacOS 10.14.3
Python 3.7.2
Napari 0.2.11

 - Please copy and paste the information at napari info option in help menubar here:

napari: 0.2.11
Platform: Darwin-18.2.0-x86_64-i386-64bit
Python: 3.7.2 (v3.7.2:9a3ffc0492, Dec 24 2018, 02:44:43) [Clang 6.0 (clang-600.0.57)]
Qt: 5.13.0
PyQt5: 5.13.0
VisPy: 0.6.4
NumPy: 1.16.4
SciPy: 1.2.1
scikit-image: 0.15.0
Dask: 2.7.0

GL version: 2.1 INTEL-12.4.7
MAX_TEXTURE_SIZE: 16384




Discussion:
This looks like a relatively straight forward bug with our thumbnail generation

<details>

```python-traceback
Traceback (most recent call last):
  File "/Users/nsofroniew/GitHub/napari/napari/_qt/qt_viewer_buttons.py", line 154, in change_ndisplay
    self.viewer.dims.ndisplay = 3
  File "/Users/nsofroniew/GitHub/napari/napari/components/dims.py", line 296, in ndisplay
    self.events.ndisplay()
  File "/Users/nsofroniew/GitHub/napari/napari/utils/event.py", line 508, in __call__
    self._invoke_callback(cb, event)
  File "/Users/nsofroniew/GitHub/napari/napari/utils/event.py", line 529, in _invoke_callback
    cb_event=(cb, event),
  File "/Users/nsofroniew/GitHub/napari/napari/utils/event.py", line 523, in _invoke_callback
    cb(event)
  File "/Users/nsofroniew/GitHub/napari/napari/components/viewer_model.py", line 400, in _update_layers
    layer.dims.ndisplay = self.dims.ndisplay
  File "/Users/nsofroniew/GitHub/napari/napari/components/dims.py", line 296, in ndisplay
    self.events.ndisplay()
  File "/Users/nsofroniew/GitHub/napari/napari/utils/event.py", line 508, in __call__
    self._invoke_callback(cb, event)
  File "/Users/nsofroniew/GitHub/napari/napari/utils/event.py", line 529, in _invoke_callback
    cb_event=(cb, event),
  File "/Users/nsofroniew/GitHub/napari/napari/utils/event.py", line 523, in _invoke_callback
    cb(event)
  File "/Users/nsofroniew/GitHub/napari/napari/layers/base/base.py", line 383, in _update_dims
    self.refresh()
  File "/Users/nsofroniew/GitHub/napari/napari/layers/base/base.py", line 602, in refresh
    self._update_thumbnail()
  File "/Users/nsofroniew/GitHub/napari/napari/layers/points/points.py", line 1322, in _update_thumbnail
    colormapped[coords[:, 0], coords[:, 1]] = colors
ValueError: shape mismatch: value array of shape (1025,4) could not be broadcast to indexing result of shape (1024,4)
```

</details>

determined by our usage of a max number of points to use for thumbnail generation - https://github.com/napari/napari/blob/master/napari/layers/points/points.py#L205

@kevinyamauchi can you take on fixing this?
Thanks for the follow-up and link to the code. Next time I run into a bug like this I will try and take the time to fix it before posting.
> Thanks for the follow-up and link to the code. Next time I run into a bug like this I will try and take the time to fix it before posting.

No worries! I really appreciate you flagging the bug. I am happy to make a fix today!
> Next time I run into a bug like this I will try and take the time to fix it before posting.

@cudmore you're also more than welcome to post bugs right away, ask for pointers, and then try and fix them yourself - no need to struggle alone first!!

Resolving patch:
diff --git a/napari/layers/points/points.py b/napari/layers/points/points.py
--- a/napari/layers/points/points.py
+++ b/napari/layers/points/points.py
@@ -1306,19 +1306,20 @@ def _update_thumbnail(self):
                 self._thumbnail_shape[:2], shape[-2:]
             ).min()
             if len(self._data_view) > self._max_points_thumbnail:
-                inds = np.random.randint(
+                thumbnail_indices = np.random.randint(
                     0, len(self._data_view), self._max_points_thumbnail
                 )
-                points = self._data_view[inds]
+                points = self._data_view[thumbnail_indices]
             else:
                 points = self._data_view
+                thumbnail_indices = self._indices_view
             coords = np.floor(
                 (points[:, -2:] - min_vals[-2:] + 0.5) * zoom_factor
             ).astype(int)
             coords = np.clip(
                 coords, 0, np.subtract(self._thumbnail_shape[:2], 1)
             )
-            colors = self.face_color[self._indices_view]
+            colors = self.face_color[thumbnail_indices]
             colormapped[coords[:, 0], coords[:, 1]] = colors
 
         colormapped[..., 3] *= self.opacity


Test patch:
diff --git a/napari/layers/points/_tests/test_points.py b/napari/layers/points/_tests/test_points.py
--- a/napari/layers/points/_tests/test_points.py
+++ b/napari/layers/points/_tests/test_points.py
@@ -1051,6 +1051,26 @@ def test_thumbnail():
     assert layer.thumbnail.shape == layer._thumbnail_shape
 
 
+def test_thumbnail_with_n_points_greater_than_max():
+    """Test thumbnail generation with n_points > _max_points_thumbnail
+
+    see: https://github.com/napari/napari/pull/934
+    """
+    # 2D
+    max_points = Points._max_points_thumbnail * 2
+    bigger_data = np.random.randint(10, 100, (max_points, 2))
+    big_layer = Points(bigger_data)
+    big_layer._update_thumbnail()
+    assert big_layer.thumbnail.shape == big_layer._thumbnail_shape
+
+    # #3D
+    bigger_data_3d = np.random.randint(10, 100, (max_points, 3))
+    bigger_layer_3d = Points(bigger_data_3d)
+    bigger_layer_3d.dims.ndisplay = 3
+    bigger_layer_3d._update_thumbnail()
+    assert bigger_layer_3d.thumbnail.shape == bigger_layer_3d._thumbnail_shape
+
+
 def test_xml_list():
     """Test the xml generation."""
     shape = (10, 2)