Repository: napari/napari

Issue:
calc_data_range() fails for large 1d data
## 🐛 Bug

Currently, the calculation of data ranges fails for large (`len > 1e7`) one-dimensional arrays: 

```python
import numpy as np
import napari
from napari.layers.utils.layer_utils import calc_data_range

n = 2*10**7 

calc_data_range(np.ones(n))

>>> IndexError: tuple index out of range
```

which e.g. will be triggered when constructing `Surface` layers with a large number of vertices/values:

```python 
vertices, faces, values = np.ones((n,3)), np.array([[0,1,2]]), np.ones(n)
napari.view_surface((vertices, faces, values))

>>> IndexError: tuple index out of range
```

## Environment

```
napari: 0.4.13.dev52+gf44a666d.d20211122
Platform: macOS-11.6-arm64-arm-64bit
System: MacOS 11.6
Python: 3.9.7 | packaged by conda-forge | (default, Sep 2 2021, 17:55:16) [Clang 11.1.0 ]
Qt: 5.15.2
PyQt5: 5.15.4
NumPy: 1.20.0
SciPy: 1.7.0
Dask: 2021.09.0
VisPy: 0.9.3

```


Discussion:
Thanks for flag. this should be a pretty straight-forward fix. Maybe @grlee77 can take it on when he gets a chance
sure, I will take a look

Resolving patch:
diff --git a/napari/layers/utils/layer_utils.py b/napari/layers/utils/layer_utils.py
--- a/napari/layers/utils/layer_utils.py
+++ b/napari/layers/utils/layer_utils.py
@@ -84,7 +84,20 @@ def calc_data_range(data, rgb=False):
     """
     if data.dtype == np.uint8:
         return [0, 255]
-    if np.prod(data.shape) > 1e7:
+
+    if data.size > 1e7 and (data.ndim == 1 or (rgb and data.ndim == 2)):
+        # If data is very large take the average of start, middle and end.
+        center = int(data.shape[0] // 2)
+        slices = [
+            slice(0, 4096),
+            slice(center - 2048, center + 2048),
+            slice(-4096, None),
+        ]
+        reduced_data = [
+            [np.max(data[sl]) for sl in slices],
+            [np.min(data[sl]) for sl in slices],
+        ]
+    elif data.size > 1e7:
         # If data is very large take the average of the top, bottom, and
         # middle slices
         offset = 2 + int(rgb)
@@ -98,12 +111,12 @@ def calc_data_range(data, rgb=False):
             and data.shape[-offset] > 64
             and data.shape[-offset + 1] > 64
         ):
-            # Find a centeral patch of the image to take
+            # Find a central patch of the image to take
             center = [int(s // 2) for s in data.shape[-offset:]]
-            cental_slice = tuple(slice(c - 31, c + 31) for c in center[:2])
+            central_slice = tuple(slice(c - 31, c + 31) for c in center[:2])
             reduced_data = [
-                [np.max(data[idx + cental_slice]) for idx in idxs],
-                [np.min(data[idx + cental_slice]) for idx in idxs],
+                [np.max(data[idx + central_slice]) for idx in idxs],
+                [np.min(data[idx + central_slice]) for idx in idxs],
             ]
         else:
             reduced_data = [


Test patch:
diff --git a/napari/layers/utils/_tests/test_layer_utils.py b/napari/layers/utils/_tests/test_layer_utils.py
--- a/napari/layers/utils/_tests/test_layer_utils.py
+++ b/napari/layers/utils/_tests/test_layer_utils.py
@@ -17,6 +17,10 @@
     size=(100_000, 1000, 1000), chunks=(1, 1000, 1000)
 )
 
+data_dask_1d = da.random.random(size=(20_000_000,), chunks=(5000,))
+
+data_dask_1d_rgb = da.random.random(size=(5_000_000, 3), chunks=(50_000, 3))
+
 data_dask_plane = da.random.random(
     size=(100_000, 100_000), chunks=(1000, 1000)
 )
@@ -79,6 +83,18 @@ def test_calc_data_range_fast_big():
     assert len(val) > 0
 
 
+@pytest.mark.timeout(2)
+def test_calc_data_range_fast_big_1d():
+    val = calc_data_range(data_dask_1d)
+    assert len(val) > 0
+
+
+@pytest.mark.timeout(2)
+def test_calc_data_range_fast_big_1d_rgb():
+    val = calc_data_range(data_dask_1d_rgb)
+    assert len(val) > 0
+
+
 @pytest.mark.timeout(2)
 def test_calc_data_range_fast_big_plane():
     val = calc_data_range(data_dask_plane)