We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
RuntimeWarning when creating empty points
## 🐛 Bug
not a bug exactly, but a runtime warning that users will now see when creating empty points after #1360 

```python
In [1]: viewer = napari.Viewer()     

In [2]: viewer.add_points()    
napari/napari/components/layerlist.py:209: RuntimeWarning: All-NaN axis encountered
  min_v = np.nanmin(
napari/napari/components/layerlist.py:212: RuntimeWarning: All-NaN axis encountered
  max_v = np.nanmax(
```

@sofroniewn, would it be possible to catch the all-NaN cases before running np.nanmax/min here?

https://github.com/napari/napari/blob/2315443d9bd4fb6b8cc30967cf433d017f7c8ca2/napari/components/layerlist.py#L209-L214

 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `napari/components/layerlist.py`
Content:
```
1 import numpy as np
2 import itertools
3 from typing import Optional, List
4 from ..layers import Layer
5 from ..utils.naming import inc_name_count
6 from ..utils.list import ListModel
7 
8 
9 def _add(event):
10     """When a layer is added, set its name."""
11     layers = event.source
12     layer = event.item
13     layer.name = layers._coerce_name(layer.name, layer)
14     layer.events.name.connect(lambda e: layers._update_name(e))
15     layers.unselect_all(ignore=layer)
16 
17 
18 class LayerList(ListModel):
19     """List-like layer collection with built-in reordering and callback hooks.
20 
21     Parameters
22     ----------
23     iterable : iterable
24         Iterable of napari.layer.Layer
25 
26     Attributes
27     ----------
28     events : vispy.util.event.EmitterGroup
29         Event hooks:
30             * added(item, index): whenever an item is added
31             * removed(item): whenever an item is removed
32             * reordered(): whenever the list is reordered
33     """
34 
35     def __init__(self, iterable=()):
36         super().__init__(
37             basetype=Layer,
38             iterable=iterable,
39             lookup={str: lambda q, e: q == e.name},
40         )
41 
42         self.events.added.connect(_add)
43 
44     def __newlike__(self, iterable):
45         return ListModel(self._basetype, iterable, self._lookup)
46 
47     def _coerce_name(self, name, layer=None):
48         """Coerce a name into a unique equivalent.
49 
50         Parameters
51         ----------
52         name : str
53             Original name.
54         layer : napari.layers.Layer, optional
55             Layer for which name is generated.
56 
57         Returns
58         -------
59         new_name : str
60             Coerced, unique name.
61         """
62         for _layer in self:
63             if _layer is layer:
64                 continue
65             if _layer.name == name:
66                 name = inc_name_count(name)
67 
68         return name
69 
70     def _update_name(self, event):
71         """Coerce name of the layer in `event.layer`."""
72         layer = event.source
73         layer.name = self._coerce_name(layer.name, layer)
74 
75     @property
76     def selected(self):
77         """List of selected layers."""
78         return [layer for layer in self if layer.selected]
79 
80     def move_selected(self, index, insert):
81         """Reorder list by moving the item at index and inserting it
82         at the insert index. If additional items are selected these will
83         get inserted at the insert index too. This allows for rearranging
84         the list based on dragging and dropping a selection of items, where
85         index is the index of the primary item being dragged, and insert is
86         the index of the drop location, and the selection indicates if
87         multiple items are being dragged. If the moved layer is not selected
88         select it.
89 
90         Parameters
91         ----------
92         index : int
93             Index of primary item to be moved
94         insert : int
95             Index that item(s) will be inserted at
96         """
97         total = len(self)
98         indices = list(range(total))
99         if not self[index].selected:
100             self.unselect_all()
101             self[index].selected = True
102         selected = [i for i in range(total) if self[i].selected]
103 
104         # remove all indices to be moved
105         for i in selected:
106             indices.remove(i)
107         # adjust offset based on selected indices to move
108         offset = sum([i < insert and i != index for i in selected])
109         # insert indices to be moved at correct start
110         for insert_idx, elem_idx in enumerate(selected, start=insert - offset):
111             indices.insert(insert_idx, elem_idx)
112         # reorder list
113         self[:] = self[tuple(indices)]
114 
115     def unselect_all(self, ignore=None):
116         """Unselects all layers expect any specified in ignore.
117 
118         Parameters
119         ----------
120         ignore : Layer | None
121             Layer that should not be unselected if specified.
122         """
123         for layer in self:
124             if layer.selected and layer != ignore:
125                 layer.selected = False
126 
127     def select_all(self):
128         """Selects all layers."""
129         for layer in self:
130             if not layer.selected:
131                 layer.selected = True
132 
133     def remove_selected(self):
134         """Removes selected items from list."""
135         to_delete = []
136         for i in range(len(self)):
137             if self[i].selected:
138                 to_delete.append(i)
139         to_delete.reverse()
140         for i in to_delete:
141             self.pop(i)
142         if len(to_delete) > 0:
143             first_to_delete = to_delete[-1]
144             if first_to_delete == 0 and len(self) > 0:
145                 self[0].selected = True
146             elif first_to_delete > 0:
147                 self[first_to_delete - 1].selected = True
148 
149     def select_next(self, shift=False):
150         """Selects next item from list.
151         """
152         selected = []
153         for i in range(len(self)):
154             if self[i].selected:
155                 selected.append(i)
156         if len(selected) > 0:
157             if selected[-1] == len(self) - 1:
158                 if shift is False:
159                     self.unselect_all(ignore=self[selected[-1]])
160             elif selected[-1] < len(self) - 1:
161                 if shift is False:
162                     self.unselect_all(ignore=self[selected[-1] + 1])
163                 self[selected[-1] + 1].selected = True
164         elif len(self) > 0:
165             self[-1].selected = True
166 
167     def select_previous(self, shift=False):
168         """Selects previous item from list.
169         """
170         selected = []
171         for i in range(len(self)):
172             if self[i].selected:
173                 selected.append(i)
174         if len(selected) > 0:
175             if selected[0] == 0:
176                 if shift is False:
177                     self.unselect_all(ignore=self[0])
178             elif selected[0] > 0:
179                 if shift is False:
180                     self.unselect_all(ignore=self[selected[0] - 1])
181                 self[selected[0] - 1].selected = True
182         elif len(self) > 0:
183             self[0].selected = True
184 
185     def toggle_selected_visibility(self):
186         """Toggle visibility of selected layers"""
187         for layer in self:
188             if layer.selected:
189                 layer.visible = not layer.visible
190 
191     @property
192     def _extent_world(self) -> np.ndarray:
193         """Extent of layers in world coordinates.
194 
195         Default to 2D with (0, 512) min/ max values if no data is present.
196 
197         Returns
198         -------
199         extent_world : array, shape (2, D)
200         """
201         if len(self) == 0:
202             min_v = [np.nan] * self.ndim
203             max_v = [np.nan] * self.ndim
204         else:
205             extrema = [layer._extent_world for layer in self]
206             mins = [e[0][::-1] for e in extrema]
207             maxs = [e[1][::-1] for e in extrema]
208 
209             min_v = np.nanmin(
210                 list(itertools.zip_longest(*mins, fillvalue=np.nan)), axis=1
211             )
212             max_v = np.nanmax(
213                 list(itertools.zip_longest(*maxs, fillvalue=np.nan)), axis=1
214             )
215 
216         min_vals = np.nan_to_num(min_v[::-1], nan=0)
217         max_vals = np.nan_to_num(max_v[::-1], nan=512)
218 
219         return np.vstack([min_vals, max_vals])
220 
221     @property
222     def _step_size(self) -> np.ndarray:
223         """Ideal step size between planes in world coordinates.
224 
225         Computes the best step size that allows all data planes to be
226         sampled if moving through the full range of world coordinates.
227         The current implementation just takes the minimum scale.
228 
229         Returns
230         -------
231         step_size : array, shape (D,)
232         """
233         if len(self) == 0:
234             return np.ones(self.ndim)
235         else:
236             scales = [layer.scale[::-1] for layer in self]
237             full_scales = list(
238                 np.array(
239                     list(itertools.zip_longest(*scales, fillvalue=np.nan))
240                 ).T
241             )
242             min_scales = np.nanmin(full_scales, axis=0)
243             return min_scales[::-1]
244 
245     @property
246     def ndim(self) -> int:
247         """Maximum dimensionality of layers.
248 
249         Defaults to 2 if no data is present.
250 
251         Returns
252         -------
253         ndim : int
254         """
255         return max((layer.ndim for layer in self), default=2)
256 
257     def save(
258         self,
259         path: str,
260         *,
261         selected: bool = False,
262         plugin: Optional[str] = None,
263     ) -> List[str]:
264         """Save all or only selected layers to a path using writer plugins.
265 
266         If ``plugin`` is not provided and only one layer is targeted, then we
267         directly call the corresponding``napari_write_<layer_type>`` hook (see
268         :ref:`single layer writer hookspecs <write-single-layer-hookspecs>`)
269         which will loop through implementations and stop when the first one
270         returns a non-``None`` result. The order in which implementations are
271         called can be changed with the Plugin sorter in the GUI or with the
272         corresponding hook's
273         :meth:`~napari.plugins._hook_callers._HookCaller.bring_to_front`
274         method.
275 
276         If ``plugin`` is not provided and multiple layers are targeted,
277         then we call
278         :meth:`~napari.plugins.hook_specifications.napari_get_writer` which
279         loops through plugins to find the first one that knows how to handle
280         the combination of layers and is able to write the file. If no plugins
281         offer :meth:`~napari.plugins.hook_specifications.napari_get_writer` for
282         that combination of layers then the default
283         :meth:`~napari.plugins.hook_specifications.napari_get_writer` will
284         create a folder and call ``napari_write_<layer_type>`` for each layer
285         using the ``Layer.name`` variable to modify the path such that the
286         layers are written to unique files in the folder.
287 
288         If ``plugin`` is provided and a single layer is targeted, then we
289         call the ``napari_write_<layer_type>`` for that plugin, and if it fails
290         we error.
291 
292         If ``plugin`` is provided and multiple layers are targeted, then
293         we call we call
294         :meth:`~napari.plugins.hook_specifications.napari_get_writer` for
295         that plugin, and if it doesn’t return a ``WriterFunction`` we error,
296         otherwise we call it and if that fails if it we error.
297 
298         Parameters
299         ----------
300         path : str
301             A filepath, directory, or URL to open.  Extensions may be used to
302             specify output format (provided a plugin is available for the
303             requested format).
304         selected : bool
305             Optional flag to only save selected layers. False by default.
306         plugin : str, optional
307             Name of the plugin to use for saving. If None then all plugins
308             corresponding to appropriate hook specification will be looped
309             through to find the first one that can save the data.
310 
311         Returns
312         -------
313         list of str
314             File paths of any files that were written.
315         """
316         from ..plugins.io import save_layers
317 
318         layers = self.selected if selected else list(self)
319 
320         if not layers:
321             import warnings
322 
323             warnings.warn(f"No layers {'selected' if selected else 'to save'}")
324             return []
325 
326         return save_layers(path, layers, plugin=plugin)
327
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/napari/components/layerlist.py b/napari/components/layerlist.py
--- a/napari/components/layerlist.py
+++ b/napari/components/layerlist.py
@@ -1,6 +1,7 @@
 import numpy as np
 import itertools
 from typing import Optional, List
+import warnings
 from ..layers import Layer
 from ..utils.naming import inc_name_count
 from ..utils.list import ListModel
@@ -206,12 +207,23 @@
             mins = [e[0][::-1] for e in extrema]
             maxs = [e[1][::-1] for e in extrema]
 
-            min_v = np.nanmin(
-                list(itertools.zip_longest(*mins, fillvalue=np.nan)), axis=1
-            )
-            max_v = np.nanmax(
-                list(itertools.zip_longest(*maxs, fillvalue=np.nan)), axis=1
-            )
+            with warnings.catch_warnings():
+                # Taking the nanmin and nanmax of an axis of all nan
+                # raises a warning and returns nan for that axis
+                # as we have do an explict nan_to_num below this
+                # behaviour is acceptable and we can filter the
+                # warning
+                warnings.filterwarnings(
+                    'ignore', message='All-NaN axis encountered'
+                )
+                min_v = np.nanmin(
+                    list(itertools.zip_longest(*mins, fillvalue=np.nan)),
+                    axis=1,
+                )
+                max_v = np.nanmax(
+                    list(itertools.zip_longest(*maxs, fillvalue=np.nan)),
+                    axis=1,
+                )
 
         min_vals = np.nan_to_num(min_v[::-1], nan=0)
         max_vals = np.nan_to_num(max_v[::-1], nan=512)
@@ -318,8 +330,6 @@
         layers = self.selected if selected else list(self)
 
         if not layers:
-            import warnings
-
             warnings.warn(f"No layers {'selected' if selected else 'to save'}")
             return []

{"golden_diff": "diff --git a/napari/components/layerlist.py b/napari/components/layerlist.py\n--- a/napari/components/layerlist.py\n+++ b/napari/components/layerlist.py\n@@ -1,6 +1,7 @@\n import numpy as np\n import itertools\n from typing import Optional, List\n+import warnings\n from ..layers import Layer\n from ..utils.naming import inc_name_count\n from ..utils.list import ListModel\n@@ -206,12 +207,23 @@\n             mins = [e[0][::-1] for e in extrema]\n             maxs = [e[1][::-1] for e in extrema]\n \n-            min_v = np.nanmin(\n-                list(itertools.zip_longest(*mins, fillvalue=np.nan)), axis=1\n-            )\n-            max_v = np.nanmax(\n-                list(itertools.zip_longest(*maxs, fillvalue=np.nan)), axis=1\n-            )\n+            with warnings.catch_warnings():\n+                # Taking the nanmin and nanmax of an axis of all nan\n+                # raises a warning and returns nan for that axis\n+                # as we have do an explict nan_to_num below this\n+                # behaviour is acceptable and we can filter the\n+                # warning\n+                warnings.filterwarnings(\n+                    'ignore', message='All-NaN axis encountered'\n+                )\n+                min_v = np.nanmin(\n+                    list(itertools.zip_longest(*mins, fillvalue=np.nan)),\n+                    axis=1,\n+                )\n+                max_v = np.nanmax(\n+                    list(itertools.zip_longest(*maxs, fillvalue=np.nan)),\n+                    axis=1,\n+                )\n \n         min_vals = np.nan_to_num(min_v[::-1], nan=0)\n         max_vals = np.nan_to_num(max_v[::-1], nan=512)\n@@ -318,8 +330,6 @@\n         layers = self.selected if selected else list(self)\n \n         if not layers:\n-            import warnings\n-\n             warnings.warn(f\"No layers {'selected' if selected else 'to save'}\")\n             return []\n", "issue": "RuntimeWarning when creating empty points\n## \ud83d\udc1b Bug\r\nnot a bug exactly, but a runtime warning that users will now see when creating empty points after #1360 \r\n\r\n```python\r\nIn [1]: viewer = napari.Viewer()     \r\n\r\nIn [2]: viewer.add_points()    \r\nnapari/napari/components/layerlist.py:209: RuntimeWarning: All-NaN axis encountered\r\n  min_v = np.nanmin(\r\nnapari/napari/components/layerlist.py:212: RuntimeWarning: All-NaN axis encountered\r\n  max_v = np.nanmax(\r\n```\r\n\r\n@sofroniewn, would it be possible to catch the all-NaN cases before running np.nanmax/min here?\r\n\r\nhttps://github.com/napari/napari/blob/2315443d9bd4fb6b8cc30967cf433d017f7c8ca2/napari/components/layerlist.py#L209-L214\r\n\r\n \n", "before_files": [{"content": "import numpy as np\nimport itertools\nfrom typing import Optional, List\nfrom ..layers import Layer\nfrom ..utils.naming import inc_name_count\nfrom ..utils.list import ListModel\n\n\ndef _add(event):\n    \"\"\"When a layer is added, set its name.\"\"\"\n    layers = event.source\n    layer = event.item\n    layer.name = layers._coerce_name(layer.name, layer)\n    layer.events.name.connect(lambda e: layers._update_name(e))\n    layers.unselect_all(ignore=layer)\n\n\nclass LayerList(ListModel):\n    \"\"\"List-like layer collection with built-in reordering and callback hooks.\n\n    Parameters\n    ----------\n    iterable : iterable\n        Iterable of napari.layer.Layer\n\n    Attributes\n    ----------\n    events : vispy.util.event.EmitterGroup\n        Event hooks:\n            * added(item, index): whenever an item is added\n            * removed(item): whenever an item is removed\n            * reordered(): whenever the list is reordered\n    \"\"\"\n\n    def __init__(self, iterable=()):\n        super().__init__(\n            basetype=Layer,\n            iterable=iterable,\n            lookup={str: lambda q, e: q == e.name},\n        )\n\n        self.events.added.connect(_add)\n\n    def __newlike__(self, iterable):\n        return ListModel(self._basetype, iterable, self._lookup)\n\n    def _coerce_name(self, name, layer=None):\n        \"\"\"Coerce a name into a unique equivalent.\n\n        Parameters\n        ----------\n        name : str\n            Original name.\n        layer : napari.layers.Layer, optional\n            Layer for which name is generated.\n\n        Returns\n        -------\n        new_name : str\n            Coerced, unique name.\n        \"\"\"\n        for _layer in self:\n            if _layer is layer:\n                continue\n            if _layer.name == name:\n                name = inc_name_count(name)\n\n        return name\n\n    def _update_name(self, event):\n        \"\"\"Coerce name of the layer in `event.layer`.\"\"\"\n        layer = event.source\n        layer.name = self._coerce_name(layer.name, layer)\n\n    @property\n    def selected(self):\n        \"\"\"List of selected layers.\"\"\"\n        return [layer for layer in self if layer.selected]\n\n    def move_selected(self, index, insert):\n        \"\"\"Reorder list by moving the item at index and inserting it\n        at the insert index. If additional items are selected these will\n        get inserted at the insert index too. This allows for rearranging\n        the list based on dragging and dropping a selection of items, where\n        index is the index of the primary item being dragged, and insert is\n        the index of the drop location, and the selection indicates if\n        multiple items are being dragged. If the moved layer is not selected\n        select it.\n\n        Parameters\n        ----------\n        index : int\n            Index of primary item to be moved\n        insert : int\n            Index that item(s) will be inserted at\n        \"\"\"\n        total = len(self)\n        indices = list(range(total))\n        if not self[index].selected:\n            self.unselect_all()\n            self[index].selected = True\n        selected = [i for i in range(total) if self[i].selected]\n\n        # remove all indices to be moved\n        for i in selected:\n            indices.remove(i)\n        # adjust offset based on selected indices to move\n        offset = sum([i < insert and i != index for i in selected])\n        # insert indices to be moved at correct start\n        for insert_idx, elem_idx in enumerate(selected, start=insert - offset):\n            indices.insert(insert_idx, elem_idx)\n        # reorder list\n        self[:] = self[tuple(indices)]\n\n    def unselect_all(self, ignore=None):\n        \"\"\"Unselects all layers expect any specified in ignore.\n\n        Parameters\n        ----------\n        ignore : Layer | None\n            Layer that should not be unselected if specified.\n        \"\"\"\n        for layer in self:\n            if layer.selected and layer != ignore:\n                layer.selected = False\n\n    def select_all(self):\n        \"\"\"Selects all layers.\"\"\"\n        for layer in self:\n            if not layer.selected:\n                layer.selected = True\n\n    def remove_selected(self):\n        \"\"\"Removes selected items from list.\"\"\"\n        to_delete = []\n        for i in range(len(self)):\n            if self[i].selected:\n                to_delete.append(i)\n        to_delete.reverse()\n        for i in to_delete:\n            self.pop(i)\n        if len(to_delete) > 0:\n            first_to_delete = to_delete[-1]\n            if first_to_delete == 0 and len(self) > 0:\n                self[0].selected = True\n            elif first_to_delete > 0:\n                self[first_to_delete - 1].selected = True\n\n    def select_next(self, shift=False):\n        \"\"\"Selects next item from list.\n        \"\"\"\n        selected = []\n        for i in range(len(self)):\n            if self[i].selected:\n                selected.append(i)\n        if len(selected) > 0:\n            if selected[-1] == len(self) - 1:\n                if shift is False:\n                    self.unselect_all(ignore=self[selected[-1]])\n            elif selected[-1] < len(self) - 1:\n                if shift is False:\n                    self.unselect_all(ignore=self[selected[-1] + 1])\n                self[selected[-1] + 1].selected = True\n        elif len(self) > 0:\n            self[-1].selected = True\n\n    def select_previous(self, shift=False):\n        \"\"\"Selects previous item from list.\n        \"\"\"\n        selected = []\n        for i in range(len(self)):\n            if self[i].selected:\n                selected.append(i)\n        if len(selected) > 0:\n            if selected[0] == 0:\n                if shift is False:\n                    self.unselect_all(ignore=self[0])\n            elif selected[0] > 0:\n                if shift is False:\n                    self.unselect_all(ignore=self[selected[0] - 1])\n                self[selected[0] - 1].selected = True\n        elif len(self) > 0:\n            self[0].selected = True\n\n    def toggle_selected_visibility(self):\n        \"\"\"Toggle visibility of selected layers\"\"\"\n        for layer in self:\n            if layer.selected:\n                layer.visible = not layer.visible\n\n    @property\n    def _extent_world(self) -> np.ndarray:\n        \"\"\"Extent of layers in world coordinates.\n\n        Default to 2D with (0, 512) min/ max values if no data is present.\n\n        Returns\n        -------\n        extent_world : array, shape (2, D)\n        \"\"\"\n        if len(self) == 0:\n            min_v = [np.nan] * self.ndim\n            max_v = [np.nan] * self.ndim\n        else:\n            extrema = [layer._extent_world for layer in self]\n            mins = [e[0][::-1] for e in extrema]\n            maxs = [e[1][::-1] for e in extrema]\n\n            min_v = np.nanmin(\n                list(itertools.zip_longest(*mins, fillvalue=np.nan)), axis=1\n            )\n            max_v = np.nanmax(\n                list(itertools.zip_longest(*maxs, fillvalue=np.nan)), axis=1\n            )\n\n        min_vals = np.nan_to_num(min_v[::-1], nan=0)\n        max_vals = np.nan_to_num(max_v[::-1], nan=512)\n\n        return np.vstack([min_vals, max_vals])\n\n    @property\n    def _step_size(self) -> np.ndarray:\n        \"\"\"Ideal step size between planes in world coordinates.\n\n        Computes the best step size that allows all data planes to be\n        sampled if moving through the full range of world coordinates.\n        The current implementation just takes the minimum scale.\n\n        Returns\n        -------\n        step_size : array, shape (D,)\n        \"\"\"\n        if len(self) == 0:\n            return np.ones(self.ndim)\n        else:\n            scales = [layer.scale[::-1] for layer in self]\n            full_scales = list(\n                np.array(\n                    list(itertools.zip_longest(*scales, fillvalue=np.nan))\n                ).T\n            )\n            min_scales = np.nanmin(full_scales, axis=0)\n            return min_scales[::-1]\n\n    @property\n    def ndim(self) -> int:\n        \"\"\"Maximum dimensionality of layers.\n\n        Defaults to 2 if no data is present.\n\n        Returns\n        -------\n        ndim : int\n        \"\"\"\n        return max((layer.ndim for layer in self), default=2)\n\n    def save(\n        self,\n        path: str,\n        *,\n        selected: bool = False,\n        plugin: Optional[str] = None,\n    ) -> List[str]:\n        \"\"\"Save all or only selected layers to a path using writer plugins.\n\n        If ``plugin`` is not provided and only one layer is targeted, then we\n        directly call the corresponding``napari_write_<layer_type>`` hook (see\n        :ref:`single layer writer hookspecs <write-single-layer-hookspecs>`)\n        which will loop through implementations and stop when the first one\n        returns a non-``None`` result. The order in which implementations are\n        called can be changed with the Plugin sorter in the GUI or with the\n        corresponding hook's\n        :meth:`~napari.plugins._hook_callers._HookCaller.bring_to_front`\n        method.\n\n        If ``plugin`` is not provided and multiple layers are targeted,\n        then we call\n        :meth:`~napari.plugins.hook_specifications.napari_get_writer` which\n        loops through plugins to find the first one that knows how to handle\n        the combination of layers and is able to write the file. If no plugins\n        offer :meth:`~napari.plugins.hook_specifications.napari_get_writer` for\n        that combination of layers then the default\n        :meth:`~napari.plugins.hook_specifications.napari_get_writer` will\n        create a folder and call ``napari_write_<layer_type>`` for each layer\n        using the ``Layer.name`` variable to modify the path such that the\n        layers are written to unique files in the folder.\n\n        If ``plugin`` is provided and a single layer is targeted, then we\n        call the ``napari_write_<layer_type>`` for that plugin, and if it fails\n        we error.\n\n        If ``plugin`` is provided and multiple layers are targeted, then\n        we call we call\n        :meth:`~napari.plugins.hook_specifications.napari_get_writer` for\n        that plugin, and if it doesn\u2019t return a ``WriterFunction`` we error,\n        otherwise we call it and if that fails if it we error.\n\n        Parameters\n        ----------\n        path : str\n            A filepath, directory, or URL to open.  Extensions may be used to\n            specify output format (provided a plugin is available for the\n            requested format).\n        selected : bool\n            Optional flag to only save selected layers. False by default.\n        plugin : str, optional\n            Name of the plugin to use for saving. If None then all plugins\n            corresponding to appropriate hook specification will be looped\n            through to find the first one that can save the data.\n\n        Returns\n        -------\n        list of str\n            File paths of any files that were written.\n        \"\"\"\n        from ..plugins.io import save_layers\n\n        layers = self.selected if selected else list(self)\n\n        if not layers:\n            import warnings\n\n            warnings.warn(f\"No layers {'selected' if selected else 'to save'}\")\n            return []\n\n        return save_layers(path, layers, plugin=plugin)\n", "path": "napari/components/layerlist.py"}], "after_files": [{"content": "import numpy as np\nimport itertools\nfrom typing import Optional, List\nimport warnings\nfrom ..layers import Layer\nfrom ..utils.naming import inc_name_count\nfrom ..utils.list import ListModel\n\n\ndef _add(event):\n    \"\"\"When a layer is added, set its name.\"\"\"\n    layers = event.source\n    layer = event.item\n    layer.name = layers._coerce_name(layer.name, layer)\n    layer.events.name.connect(lambda e: layers._update_name(e))\n    layers.unselect_all(ignore=layer)\n\n\nclass LayerList(ListModel):\n    \"\"\"List-like layer collection with built-in reordering and callback hooks.\n\n    Parameters\n    ----------\n    iterable : iterable\n        Iterable of napari.layer.Layer\n\n    Attributes\n    ----------\n    events : vispy.util.event.EmitterGroup\n        Event hooks:\n            * added(item, index): whenever an item is added\n            * removed(item): whenever an item is removed\n            * reordered(): whenever the list is reordered\n    \"\"\"\n\n    def __init__(self, iterable=()):\n        super().__init__(\n            basetype=Layer,\n            iterable=iterable,\n            lookup={str: lambda q, e: q == e.name},\n        )\n\n        self.events.added.connect(_add)\n\n    def __newlike__(self, iterable):\n        return ListModel(self._basetype, iterable, self._lookup)\n\n    def _coerce_name(self, name, layer=None):\n        \"\"\"Coerce a name into a unique equivalent.\n\n        Parameters\n        ----------\n        name : str\n            Original name.\n        layer : napari.layers.Layer, optional\n            Layer for which name is generated.\n\n        Returns\n        -------\n        new_name : str\n            Coerced, unique name.\n        \"\"\"\n        for _layer in self:\n            if _layer is layer:\n                continue\n            if _layer.name == name:\n                name = inc_name_count(name)\n\n        return name\n\n    def _update_name(self, event):\n        \"\"\"Coerce name of the layer in `event.layer`.\"\"\"\n        layer = event.source\n        layer.name = self._coerce_name(layer.name, layer)\n\n    @property\n    def selected(self):\n        \"\"\"List of selected layers.\"\"\"\n        return [layer for layer in self if layer.selected]\n\n    def move_selected(self, index, insert):\n        \"\"\"Reorder list by moving the item at index and inserting it\n        at the insert index. If additional items are selected these will\n        get inserted at the insert index too. This allows for rearranging\n        the list based on dragging and dropping a selection of items, where\n        index is the index of the primary item being dragged, and insert is\n        the index of the drop location, and the selection indicates if\n        multiple items are being dragged. If the moved layer is not selected\n        select it.\n\n        Parameters\n        ----------\n        index : int\n            Index of primary item to be moved\n        insert : int\n            Index that item(s) will be inserted at\n        \"\"\"\n        total = len(self)\n        indices = list(range(total))\n        if not self[index].selected:\n            self.unselect_all()\n            self[index].selected = True\n        selected = [i for i in range(total) if self[i].selected]\n\n        # remove all indices to be moved\n        for i in selected:\n            indices.remove(i)\n        # adjust offset based on selected indices to move\n        offset = sum([i < insert and i != index for i in selected])\n        # insert indices to be moved at correct start\n        for insert_idx, elem_idx in enumerate(selected, start=insert - offset):\n            indices.insert(insert_idx, elem_idx)\n        # reorder list\n        self[:] = self[tuple(indices)]\n\n    def unselect_all(self, ignore=None):\n        \"\"\"Unselects all layers expect any specified in ignore.\n\n        Parameters\n        ----------\n        ignore : Layer | None\n            Layer that should not be unselected if specified.\n        \"\"\"\n        for layer in self:\n            if layer.selected and layer != ignore:\n                layer.selected = False\n\n    def select_all(self):\n        \"\"\"Selects all layers.\"\"\"\n        for layer in self:\n            if not layer.selected:\n                layer.selected = True\n\n    def remove_selected(self):\n        \"\"\"Removes selected items from list.\"\"\"\n        to_delete = []\n        for i in range(len(self)):\n            if self[i].selected:\n                to_delete.append(i)\n        to_delete.reverse()\n        for i in to_delete:\n            self.pop(i)\n        if len(to_delete) > 0:\n            first_to_delete = to_delete[-1]\n            if first_to_delete == 0 and len(self) > 0:\n                self[0].selected = True\n            elif first_to_delete > 0:\n                self[first_to_delete - 1].selected = True\n\n    def select_next(self, shift=False):\n        \"\"\"Selects next item from list.\n        \"\"\"\n        selected = []\n        for i in range(len(self)):\n            if self[i].selected:\n                selected.append(i)\n        if len(selected) > 0:\n            if selected[-1] == len(self) - 1:\n                if shift is False:\n                    self.unselect_all(ignore=self[selected[-1]])\n            elif selected[-1] < len(self) - 1:\n                if shift is False:\n                    self.unselect_all(ignore=self[selected[-1] + 1])\n                self[selected[-1] + 1].selected = True\n        elif len(self) > 0:\n            self[-1].selected = True\n\n    def select_previous(self, shift=False):\n        \"\"\"Selects previous item from list.\n        \"\"\"\n        selected = []\n        for i in range(len(self)):\n            if self[i].selected:\n                selected.append(i)\n        if len(selected) > 0:\n            if selected[0] == 0:\n                if shift is False:\n                    self.unselect_all(ignore=self[0])\n            elif selected[0] > 0:\n                if shift is False:\n                    self.unselect_all(ignore=self[selected[0] - 1])\n                self[selected[0] - 1].selected = True\n        elif len(self) > 0:\n            self[0].selected = True\n\n    def toggle_selected_visibility(self):\n        \"\"\"Toggle visibility of selected layers\"\"\"\n        for layer in self:\n            if layer.selected:\n                layer.visible = not layer.visible\n\n    @property\n    def _extent_world(self) -> np.ndarray:\n        \"\"\"Extent of layers in world coordinates.\n\n        Default to 2D with (0, 512) min/ max values if no data is present.\n\n        Returns\n        -------\n        extent_world : array, shape (2, D)\n        \"\"\"\n        if len(self) == 0:\n            min_v = [np.nan] * self.ndim\n            max_v = [np.nan] * self.ndim\n        else:\n            extrema = [layer._extent_world for layer in self]\n            mins = [e[0][::-1] for e in extrema]\n            maxs = [e[1][::-1] for e in extrema]\n\n            with warnings.catch_warnings():\n                # Taking the nanmin and nanmax of an axis of all nan\n                # raises a warning and returns nan for that axis\n                # as we have do an explict nan_to_num below this\n                # behaviour is acceptable and we can filter the\n                # warning\n                warnings.filterwarnings(\n                    'ignore', message='All-NaN axis encountered'\n                )\n                min_v = np.nanmin(\n                    list(itertools.zip_longest(*mins, fillvalue=np.nan)),\n                    axis=1,\n                )\n                max_v = np.nanmax(\n                    list(itertools.zip_longest(*maxs, fillvalue=np.nan)),\n                    axis=1,\n                )\n\n        min_vals = np.nan_to_num(min_v[::-1], nan=0)\n        max_vals = np.nan_to_num(max_v[::-1], nan=512)\n\n        return np.vstack([min_vals, max_vals])\n\n    @property\n    def _step_size(self) -> np.ndarray:\n        \"\"\"Ideal step size between planes in world coordinates.\n\n        Computes the best step size that allows all data planes to be\n        sampled if moving through the full range of world coordinates.\n        The current implementation just takes the minimum scale.\n\n        Returns\n        -------\n        step_size : array, shape (D,)\n        \"\"\"\n        if len(self) == 0:\n            return np.ones(self.ndim)\n        else:\n            scales = [layer.scale[::-1] for layer in self]\n            full_scales = list(\n                np.array(\n                    list(itertools.zip_longest(*scales, fillvalue=np.nan))\n                ).T\n            )\n            min_scales = np.nanmin(full_scales, axis=0)\n            return min_scales[::-1]\n\n    @property\n    def ndim(self) -> int:\n        \"\"\"Maximum dimensionality of layers.\n\n        Defaults to 2 if no data is present.\n\n        Returns\n        -------\n        ndim : int\n        \"\"\"\n        return max((layer.ndim for layer in self), default=2)\n\n    def save(\n        self,\n        path: str,\n        *,\n        selected: bool = False,\n        plugin: Optional[str] = None,\n    ) -> List[str]:\n        \"\"\"Save all or only selected layers to a path using writer plugins.\n\n        If ``plugin`` is not provided and only one layer is targeted, then we\n        directly call the corresponding``napari_write_<layer_type>`` hook (see\n        :ref:`single layer writer hookspecs <write-single-layer-hookspecs>`)\n        which will loop through implementations and stop when the first one\n        returns a non-``None`` result. The order in which implementations are\n        called can be changed with the Plugin sorter in the GUI or with the\n        corresponding hook's\n        :meth:`~napari.plugins._hook_callers._HookCaller.bring_to_front`\n        method.\n\n        If ``plugin`` is not provided and multiple layers are targeted,\n        then we call\n        :meth:`~napari.plugins.hook_specifications.napari_get_writer` which\n        loops through plugins to find the first one that knows how to handle\n        the combination of layers and is able to write the file. If no plugins\n        offer :meth:`~napari.plugins.hook_specifications.napari_get_writer` for\n        that combination of layers then the default\n        :meth:`~napari.plugins.hook_specifications.napari_get_writer` will\n        create a folder and call ``napari_write_<layer_type>`` for each layer\n        using the ``Layer.name`` variable to modify the path such that the\n        layers are written to unique files in the folder.\n\n        If ``plugin`` is provided and a single layer is targeted, then we\n        call the ``napari_write_<layer_type>`` for that plugin, and if it fails\n        we error.\n\n        If ``plugin`` is provided and multiple layers are targeted, then\n        we call we call\n        :meth:`~napari.plugins.hook_specifications.napari_get_writer` for\n        that plugin, and if it doesn\u2019t return a ``WriterFunction`` we error,\n        otherwise we call it and if that fails if it we error.\n\n        Parameters\n        ----------\n        path : str\n            A filepath, directory, or URL to open.  Extensions may be used to\n            specify output format (provided a plugin is available for the\n            requested format).\n        selected : bool\n            Optional flag to only save selected layers. False by default.\n        plugin : str, optional\n            Name of the plugin to use for saving. If None then all plugins\n            corresponding to appropriate hook specification will be looped\n            through to find the first one that can save the data.\n\n        Returns\n        -------\n        list of str\n            File paths of any files that were written.\n        \"\"\"\n        from ..plugins.io import save_layers\n\n        layers = self.selected if selected else list(self)\n\n        if not layers:\n            warnings.warn(f\"No layers {'selected' if selected else 'to save'}\")\n            return []\n\n        return save_layers(path, layers, plugin=plugin)\n", "path": "napari/components/layerlist.py"}]}