Repository: napari/napari

Issue:
Plot lots of spots
## 🚀 Plot lots of spots
I would like to be able to plot millions of spots using napari.

## Motivation

Starfish users will often want to plot spots _after_ merging fields of view. In these cases, there will be millions of spots to plot. 

## Pitch

Right now I can plot 20k spots, which takes a while. _After_ the spots populate napari, navigating them is lightning fast. Larger groups of spots take longer than I'm willing to wait (a few minutes for 250k before I interrupt the kernel).

## Alternatives

I tried Vaex, and mayavi, and matplotlib, and could only get mayavi to work, but it ground my computer to a halt, and I had to play some tricks to kill it. 


Discussion:
Thanks for the report! As @sofroniewn mentioned on slack, this should be a matter of being more clever with thumbnails (e.g. subsampling many fewer points). The canvas is only 32x32 = 1024, so three orders of magnitude more points is kinda overkill. :joy:

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
@@ -112,6 +112,10 @@ class Points(Layer):
         None after dragging is done.
     """
 
+    # The max number of points that will ever be used to render the thumbnail
+    # If more points are present then they are randomly subsampled
+    _max_points_thumbnail = 1024
+
     def __init__(
         self,
         data,
@@ -693,8 +697,15 @@ def _update_thumbnail(self):
             zoom_factor = np.divide(
                 self._thumbnail_shape[:2], shape[-2:]
             ).min()
+            if len(self._data_view) > self._max_points_thumbnail:
+                inds = np.random.randint(
+                    0, len(self._data_view), self._max_points_thumbnail
+                )
+                points = self._data_view[inds]
+            else:
+                points = self._data_view
             coords = np.floor(
-                (self._data_view[:, -2:] - min_vals[-2:] + 0.5) * zoom_factor
+                (points[:, -2:] - min_vals[-2:] + 0.5) * zoom_factor
             ).astype(int)
             coords = np.clip(
                 coords, 0, np.subtract(self._thumbnail_shape[:2], 1)
diff --git a/napari/layers/vectors/vectors.py b/napari/layers/vectors/vectors.py
--- a/napari/layers/vectors/vectors.py
+++ b/napari/layers/vectors/vectors.py
@@ -295,6 +295,11 @@ def _update_thumbnail(self):
         zoom_factor = np.divide(self._thumbnail_shape[:2], shape).min()
 
         vectors = copy(self._data_view[:, :, -2:])
+        if len(vectors) > self._max_vectors_thumbnail:
+            inds = np.random.randint(
+                0, len(vectors), self._max_vectors_thumbnail
+            )
+            vectors = vectors[inds]
         vectors[:, 1, :] = vectors[:, 0, :] + vectors[:, 1, :] * self.length
         downsampled = (vectors - offset) * zoom_factor
         downsampled = np.clip(
@@ -303,11 +308,6 @@ def _update_thumbnail(self):
         colormapped = np.zeros(self._thumbnail_shape)
         colormapped[..., 3] = 1
         col = Color(self.edge_color).rgba
-        if len(downsampled) > self._max_vectors_thumbnail:
-            inds = np.random.randint(
-                0, len(downsampled), self._max_vectors_thumbnail
-            )
-            downsampled = downsampled[inds]
         for v in downsampled:
             start = v[0]
             stop = v[1]


Test patch: