Repository: napari/napari

Issue:
Re-setting shapes data to initial data fails, but only in 3D
@dagewa reported this [bug on imagesc](https://forum.image.sc/t/failing-to-update-shapes-layer/67010).  

This is a strange case where setting `shapes_layer.data` to the exact same data that instantiated it causes an error, but *only* if the viewer is in 3D mode (normally, we'd expect this sort of error to occur in the view logic, not in the model itself).

simple example:
```python
import numpy as np
from napari.layers import Shapes
import napari

lines = [
    np.array([[0, 0, 0], [500, 0, 0]]),
    np.array([[0, 0, 0], [0, 300, 0]]),
    np.array([[0, 0, 0], [0, 0, 200]]),
]
shapes = Shapes(lines, shape_type='line')
shapes.data = lines  # reseting the data here is fine

viewer = napari.Viewer(ndisplay=3)
viewer.add_layer(shapes)   # adds to the viewer fine

# ERROR
shapes.data = lines  # reseting the data here exactly as above fails
```

<details>

<summary>traceback</summary>

```pytb
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [43], in <cell line: 1>()
----> 1 shapes.data = lines

File ~/Dropbox (HMS)/Python/forks/napari/napari/layers/shapes/shapes.py:688, in Shapes.data(self, data)
    680     face_color = np.concatenate(
    681         (
    682             face_color,
    683             self._get_new_shape_color(n_shapes_difference, 'face'),
    684         )
    685     )
    687 self._data_view = ShapeList()
--> 688 self.add(
    689     data,
    690     shape_type=shape_type,
    691     edge_width=edge_widths,
    692     edge_color=edge_color,
    693     face_color=face_color,
    694     z_index=z_indices,
    695 )
    697 self._update_dims()
    698 self.events.data(value=self.data)

File ~/Dropbox (HMS)/Python/forks/napari/napari/layers/shapes/shapes.py:2023, in Shapes.add(self, data, shape_type, edge_width, edge_color, face_color, z_index)
   2021 self._feature_table.resize(total_shapes)
   2022 self.text.apply(self.features)
-> 2023 self._add_shapes(
   2024     data,
   2025     shape_type=shape_type,
   2026     edge_width=edge_width,
   2027     edge_color=edge_color,
   2028     face_color=face_color,
   2029     z_index=z_index,
   2030 )
   2031 self.events.data(value=self.data)

File ~/Dropbox (HMS)/Python/forks/napari/napari/layers/shapes/shapes.py:2230, in Shapes._add_shapes(self, data, shape_type, edge_width, edge_color, face_color, z_index, z_refresh)
   2220     # Turn input arguments into iterables
   2221     shape_inputs = zip(
   2222         data,
   2223         ensure_iterable(shape_type),
   (...)
   2227         ensure_iterable(z_index),
   2228     )
-> 2230     self._add_shapes_to_view(shape_inputs, self._data_view)
   2232 self._display_order_stored = copy(self._dims_order)
   2233 self._ndisplay_stored = copy(self._ndisplay)

File ~/Dropbox (HMS)/Python/forks/napari/napari/layers/shapes/shapes.py:2260, in Shapes._add_shapes_to_view(self, shape_inputs, data_view)
   2257 shapes, edge_colors, face_colors = tuple(zip(*sh_inp))
   2259 # Add all shapes at once (faster than adding them one by one)
-> 2260 data_view.add(
   2261     shape=shapes,
   2262     edge_color=edge_colors,
   2263     face_color=face_colors,
   2264     z_refresh=False,
   2265 )
   2267 data_view._update_z_order()

File ~/Dropbox (HMS)/Python/forks/napari/napari/layers/shapes/_shape_list.py:276, in ShapeList.add(self, shape, face_color, edge_color, shape_index, z_refresh)
    269     if shape_index is not None:
    270         raise ValueError(
    271             trans._(
    272                 'shape_index must be None when adding multiple shapes',
    273                 deferred=True,
    274             )
    275         )
--> 276     self._add_multiple_shapes(
    277         shapes=shape,
    278         face_colors=face_color,
    279         edge_colors=edge_color,
    280         z_refresh=z_refresh,
    281     )
    282 else:
    283     raise ValueError(
    284         trans._(
    285             'Cannot add single nor multiple shape',
    286             deferred=True,
    287         )
    288     )

File ~/Dropbox (HMS)/Python/forks/napari/napari/layers/shapes/_shape_list.py:555, in ShapeList._add_multiple_shapes(self, shapes, face_colors, edge_colors, z_refresh)
    553 self._face_color = np.vstack((self._face_color, face_colors))
    554 self._edge_color = np.vstack((self._edge_color, edge_colors))
--> 555 self._vertices = np.vstack((self._vertices, np.vstack(all_vertices)))
    556 self._index = np.append(self._index, np.concatenate(all_index), axis=0)
    558 self._mesh.vertices = np.vstack(
    559     (self._mesh.vertices, np.vstack(all_mesh_vertices))
    560 )

File <__array_function__ internals>:180, in vstack(*args, **kwargs)

File ~/miniconda3/envs/napdev/lib/python3.9/site-packages/numpy/core/shape_base.py:282, in vstack(tup)
    280 if not isinstance(arrs, list):
    281     arrs = [arrs]
--> 282 return _nx.concatenate(arrs, 0)

File <__array_function__ internals>:180, in concatenate(*args, **kwargs)

ValueError: all the input array dimensions for the concatenation axis must match exactly, but along dimension 1, the array at index 0 has size 2 and the array at index 1 has size 3
```
</details>
cc @sofroniewn, 


Discussion:
This issue has been mentioned on **Image.sc Forum**. There might be relevant details there:

https://forum.image.sc/t/failing-to-update-shapes-layer/67010/2

Maybe @Carreau can check this out - we recently made some changes in shapes - i'm not sure if this is on main or 0.4.15 though

Resolving patch:
diff --git a/napari/layers/shapes/_shape_list.py b/napari/layers/shapes/_shape_list.py
--- a/napari/layers/shapes/_shape_list.py
+++ b/napari/layers/shapes/_shape_list.py
@@ -53,7 +53,7 @@ class ShapeList:
     Notes
     -----
     _vertices : np.ndarray
-        Mx2 array of all displayed vertices from all shapes
+        MxN array of all displayed vertices from all shapes where N is equal to ndisplay
     _index : np.ndarray
         Length M array with the index (0, ..., N-1) of each shape that each
         vertex corresponds to
diff --git a/napari/layers/shapes/shapes.py b/napari/layers/shapes/shapes.py
--- a/napari/layers/shapes/shapes.py
+++ b/napari/layers/shapes/shapes.py
@@ -684,7 +684,7 @@ def data(self, data):
                 )
             )
 
-        self._data_view = ShapeList()
+        self._data_view = ShapeList(ndisplay=self._ndisplay)
         self.add(
             data,
             shape_type=shape_type,


Test patch:
diff --git a/napari/layers/shapes/_tests/test_shapes.py b/napari/layers/shapes/_tests/test_shapes.py
--- a/napari/layers/shapes/_tests/test_shapes.py
+++ b/napari/layers/shapes/_tests/test_shapes.py
@@ -2153,3 +2153,15 @@ def test_world_data_extent():
     max_val = (9, 30, 15)
     extent = np.array((min_val, max_val))
     check_layer_world_data_extent(layer, extent, (3, 1, 1), (10, 20, 5), False)
+
+
+def test_set_data_3d():
+    """Test for reproduce https://github.com/napari/napari/issues/4527"""
+    lines = [
+        np.array([[0, 0, 0], [500, 0, 0]]),
+        np.array([[0, 0, 0], [0, 300, 0]]),
+        np.array([[0, 0, 0], [0, 0, 200]]),
+    ]
+    shapes = Shapes(lines, shape_type='line')
+    shapes._ndisplay = 3
+    shapes.data = lines