Repository: napari/napari

Issue:
Points thumbnail is not centered 
## 🐛 Bug

When point coordinates are embedded in a very thin image, the image itself is centered on its thumbnail, but the points are squished at the top of the thumbnail. They should be centered just like the image instead.

## To Reproduce

Steps to reproduce the behavior:

```python
import numpy as np
import napari

image = np.random.random((10, 500))
pts = np.random.random((10, 2)) * np.array([10, 500])
with napari.gui_qt():
    v = napari.Viewer()
    v.add_image(image)
    v.add_points(pts)
```

<img width="1200" alt="Screen Shot 2020-07-14 at 10 14 55 am" src="https://user-images.githubusercontent.com/492549/87366051-1a5bc700-c5bb-11ea-8387-8746ce8af5a2.png">


## Expected behavior

The points should be vertically centered in the thumbnail.


Discussion:

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
@@ -1358,6 +1358,7 @@ def _update_thumbnail(self):
         colormapped[..., 3] = 1
         view_data = self._view_data
         if len(view_data) > 0:
+            # Get the zoom factor required to fit all data in the thumbnail.
             de = self._extent_data
             min_vals = [de[0, i] for i in self._dims_displayed]
             shape = np.ceil(
@@ -1366,6 +1367,8 @@ def _update_thumbnail(self):
             zoom_factor = np.divide(
                 self._thumbnail_shape[:2], shape[-2:]
             ).min()
+
+            # Maybe subsample the points.
             if len(view_data) > self._max_points_thumbnail:
                 thumbnail_indices = np.random.randint(
                     0, len(view_data), self._max_points_thumbnail
@@ -1374,12 +1377,21 @@ def _update_thumbnail(self):
             else:
                 points = view_data
                 thumbnail_indices = self._indices_view
+
+            # Calculate the point coordinates in the thumbnail data space.
+            thumbnail_shape = np.clip(
+                np.ceil(zoom_factor * np.array(shape[:2])).astype(int),
+                1,  # smallest side should be 1 pixel wide
+                self._thumbnail_shape[:2],
+            )
             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)
-            )
+            coords = np.clip(coords, 0, thumbnail_shape - 1)
+
+            # Draw single pixel points in the colormapped thumbnail.
+            colormapped = np.zeros(tuple(thumbnail_shape) + (4,))
+            colormapped[..., 3] = 1
             colors = self._face.colors[thumbnail_indices]
             colormapped[coords[:, 0], coords[:, 1]] = colors
 


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
@@ -1530,6 +1530,37 @@ def test_thumbnail():
     assert layer.thumbnail.shape == layer._thumbnail_shape
 
 
+def test_thumbnail_non_square_data():
+    """Test the image thumbnail for non-square data.
+
+    See: https://github.com/napari/napari/issues/1450
+    """
+    # The points coordinates are in a short and wide range.
+    data_range = [1, 32]
+    np.random.seed(0)
+    data = np.random.random((10, 2)) * data_range
+    # Make sure the random points span the range.
+    data[0, :] = [0, 0]
+    data[-1, :] = data_range
+    layer = Points(data)
+
+    layer._update_thumbnail()
+
+    assert layer.thumbnail.shape == layer._thumbnail_shape
+    # Check that the thumbnail only contains non-zero RGB values in the middle two rows.
+    mid_row = layer.thumbnail.shape[0] // 2
+    expected_zeros = np.zeros(shape=(mid_row - 1, 32, 3), dtype=np.uint8)
+    np.testing.assert_array_equal(
+        layer.thumbnail[: mid_row - 1, :, :3], expected_zeros
+    )
+    assert (
+        np.count_nonzero(layer.thumbnail[mid_row - 1 : mid_row + 1, :, :3]) > 0
+    )
+    np.testing.assert_array_equal(
+        layer.thumbnail[mid_row + 1 :, :, :3], expected_zeros
+    )
+
+
 def test_thumbnail_with_n_points_greater_than_max():
     """Test thumbnail generation with n_points > _max_points_thumbnail