internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: napari/napari

Issue:
Settings manager may need to handle edge case where loaded data is None
## 🐛 Bug
Looks like the settings manager `_load` method may need to handle the case where `safe_load` returns `None`.  I don't yet have a reproducible example... but I'm working on some stuff that is crashing napari a lot :joy:, so maybe settings aren't getting written correctly at close?  and during one of my runs I got this traceback:

```pytb
  File "/Users/talley/Desktop/t.py", line 45, in <module>
    import napari
  File "/Users/talley/Dropbox (HMS)/Python/forks/napari/napari/__init__.py", line 22, in <module>
    from ._event_loop import gui_qt, run
  File "/Users/talley/Dropbox (HMS)/Python/forks/napari/napari/_event_loop.py", line 2, in <module>
    from ._qt.qt_event_loop import gui_qt, run
  File "/Users/talley/Dropbox (HMS)/Python/forks/napari/napari/_qt/__init__.py", line 41, in <module>
    from .qt_main_window import Window
  File "/Users/talley/Dropbox (HMS)/Python/forks/napari/napari/_qt/qt_main_window.py", line 30, in <module>
    from ..utils.settings import SETTINGS
  File "/Users/talley/Dropbox (HMS)/Python/forks/napari/napari/utils/settings/__init__.py", line 5, in <module>
    from ._manager import SETTINGS
  File "/Users/talley/Dropbox (HMS)/Python/forks/napari/napari/utils/settings/_manager.py", line 177, in <module>
    SETTINGS = SettingsManager()
  File "/Users/talley/Dropbox (HMS)/Python/forks/napari/napari/utils/settings/_manager.py", line 66, in __init__
    self._load()
  File "/Users/talley/Dropbox (HMS)/Python/forks/napari/napari/utils/settings/_manager.py", line 115, in _load
    for section, model_data in data.items():
AttributeError: 'NoneType' object has no attribute 'items'
```


Discussion:
we're seeing this in the tests for tutorials too: https://github.com/napari/tutorials/pull/111
@goanpeca 
Whoops. Thanks for the report @tlambert03, indeed not cool. Will take a look.

Resolving patch:
diff --git a/napari/utils/settings/_manager.py b/napari/utils/settings/_manager.py
--- a/napari/utils/settings/_manager.py
+++ b/napari/utils/settings/_manager.py
@@ -108,10 +108,11 @@ def _load(self):
             section = self._get_section_name(plugin)
             self._defaults[section] = plugin()
             self._models[section] = plugin
+            self._settings[section] = plugin()
 
         if path.is_file():
             with open(path) as fh:
-                data = safe_load(fh.read())
+                data = safe_load(fh.read()) or {}
 
             # Check with models
             for section, model_data in data.items():
@@ -138,8 +139,6 @@ def _load(self):
                     model = self._models[section](**model_data)
                     model.events.connect(lambda x: self._save())
                     self._settings[section] = model
-        else:
-            self._settings = self._defaults
 
         self._save()
 


Test patch:
Repository: napari/napari

Issue:
Using escape key permanently hides preferences window
## 🐛 Bug
Related to #2527 ... but perhaps different enough for a different issue:

If you use the escape key when the preferences window is open, it will indeed close the window.  However, using command-W or File > Preferences will not re-show the window.


Discussion:
Buuug!

Resolving patch:
diff --git a/napari/_qt/dialogs/preferences_dialog.py b/napari/_qt/dialogs/preferences_dialog.py
--- a/napari/_qt/dialogs/preferences_dialog.py
+++ b/napari/_qt/dialogs/preferences_dialog.py
@@ -76,6 +76,11 @@ def closeEvent(self, event):
         self.closed.emit()
         super().closeEvent(event)
 
+    def reject(self):
+        """Override to handle Escape."""
+        super().reject()
+        self.close()
+
     def resizeEvent(self, event):
         """Override to emit signal."""
         self.resized.emit(event.size())


Test patch:
Repository: napari/napari

Issue:
Better handling of `cmd-W` with multiple non-modal windows
## 🐛 Bug

The addition of some new non-modal windows is revealing a little bug in our handling of window keyboard shortcuts like command-W.  As an example, if you were to hit command-comma to open the preferences window, I would expect command-w to then close the preferences window and return you to what you were doing.  However, command-w is tied to the `viewer.window._qt_main_window`, and since the app is set to quit when the last main window is closed, the entire program just quits (assuming you have only one viewer).


Discussion:
Indeed!, I am really wondering how this works on other OSs, any input there?

Should we add this on all OSs or is this more of a Mac thing?

And yes, indeed the `Cmd+W` (should close the active window, whatever that is.)


> And yes, indeed the Cmd+W (should close the active window, whatever that is.)

My preference (as a mac user) would be this too, it's tripped me up a couple times also!!

Resolving patch:
diff --git a/napari/_qt/qt_main_window.py b/napari/_qt/qt_main_window.py
--- a/napari/_qt/qt_main_window.py
+++ b/napari/_qt/qt_main_window.py
@@ -13,6 +13,7 @@
 from qtpy.QtWidgets import (
     QAction,
     QApplication,
+    QDialog,
     QDockWidget,
     QHBoxLayout,
     QLabel,
@@ -195,6 +196,20 @@ def close(self, quit_app=False):
         self._quit_app = quit_app
         return super().close()
 
+    def close_window(self):
+        """Close active dialog or active window."""
+        parent = QApplication.focusWidget()
+        while parent is not None:
+            if isinstance(parent, QMainWindow):
+                self.close()
+                break
+
+            if isinstance(parent, QDialog):
+                parent.close()
+                break
+
+            parent = parent.parent()
+
     def closeEvent(self, event):
         """This method will be called when the main window is closing.
 
@@ -432,7 +447,7 @@ def _add_file_menu(self):
 
         closeAction = QAction(trans._('Close Window'), self._qt_window)
         closeAction.setShortcut('Ctrl+W')
-        closeAction.triggered.connect(self._qt_window.close)
+        closeAction.triggered.connect(self._qt_window.close_window)
 
         from ..plugins import _sample_data
 


Test patch:
Repository: napari/napari

Issue:
Getting deprecation warning when saving data
## 🐛 Bug

When saving data, this line https://github.com/napari/napari/blob/25e08d6659427a57a9c33d09b37aac724d91ea43/napari/_qt/qt_viewer.py#L451 is triggering a deprecation warning. We should fix before our next release. Should be a one-liner

```
/Users/nsofroniew/GitHub/napari/napari/_qt/qt_viewer.py:442: FutureWarning: 'viewer.layers.selected' is deprecated and will be removed in or after v0.4.9. Please use 'viewer.layers.selection'!
```




Discussion:
Hello @sofroniewn, I would like to work on this issue. Could you please assign it to me? Thank you.

Resolving patch:
diff --git a/napari/_qt/qt_viewer.py b/napari/_qt/qt_viewer.py
--- a/napari/_qt/qt_viewer.py
+++ b/napari/_qt/qt_viewer.py
@@ -448,7 +448,7 @@ def _save_layers_dialog(self, selected=False):
         msg = ''
         if not len(self.viewer.layers):
             msg = trans._("There are no layers in the viewer to save")
-        elif selected and not len(self.viewer.layers.selected):
+        elif selected and not len(self.viewer.layers.selection):
             msg = trans._(
                 'Please select one or more layers to save,'
                 '\nor use "Save all layers..."'


Test patch:
Repository: napari/napari

Issue:
Use static base class for Image if possible
## 🐛 Bug
tiny thing, but don't want to forget it... in `image.py`, this dynamic inheritance pattern breaks IDE inspection for anything annotated as an `Image`

```
_image_base_class = _get_image_base_class()

class Image(_image_base_class):
```

would be nice to figure out a way to accomplish the same goal without the dynamic base class.


Discussion:
>would be nice to figure out a way to accomplish the same goal without the dynamic base class.

Any more details here can help @tlambert03 :) 
sorry, will take care of it! :)

Resolving patch:
diff --git a/napari/layers/image/image.py b/napari/layers/image/image.py
--- a/napari/layers/image/image.py
+++ b/napari/layers/image/image.py
@@ -764,20 +764,7 @@ def on_chunk_loaded(self, request: ChunkRequest) -> None:
             self._on_data_loaded(data, sync=False)
 
 
-def _get_image_base_class() -> _ImageBase:
-    """Return Image or OctreeImage based config settings."""
-    if config.async_octree:
-        from ..image.experimental.octree_image import _OctreeImageBase
-
-        return _OctreeImageBase
-
-    return _ImageBase
-
-
-_image_base_class = _get_image_base_class()
-
-
-class Image(_image_base_class):
+class Image(_ImageBase):
     def _get_state(self):
         """Get dictionary of layer state.
 
@@ -802,3 +789,10 @@ def _get_state(self):
             }
         )
         return state
+
+
+if config.async_octree:
+    from ..image.experimental.octree_image import _OctreeImageBase
+
+    class Image(Image, _OctreeImageBase):
+        pass
diff --git a/napari/layers/labels/labels.py b/napari/layers/labels/labels.py
--- a/napari/layers/labels/labels.py
+++ b/napari/layers/labels/labels.py
@@ -5,6 +5,7 @@
 import numpy as np
 from scipy import ndimage as ndi
 
+from ...utils import config
 from ...utils.colormaps import (
     color_dict_to_colormap,
     label_colormap,
@@ -14,17 +15,15 @@
 from ...utils.events.event import WarningEmitter
 from ...utils.translations import trans
 from ..image._image_utils import guess_multiscale
-from ..image.image import _get_image_base_class
+from ..image.image import _ImageBase
 from ..utils.color_transformations import transform_color
 from ..utils.layer_utils import dataframe_to_properties
 from ._labels_constants import LabelBrushShape, LabelColorMode, Mode
 from ._labels_mouse_bindings import draw, pick
 from ._labels_utils import indices_in_shape, sphere_indices
 
-_image_base_class = _get_image_base_class()
 
-
-class Labels(_image_base_class):
+class Labels(_ImageBase):
     """Labels (or segmentation) layer.
 
     An image-like layer where every pixel contains an integer ID
@@ -1123,3 +1122,10 @@ def get_status(self, position=None, world=False):
                 else:
                     msg += ' ' + trans._('[No Properties]')
         return msg
+
+
+if config.async_octree:
+    from ..image.experimental.octree_image import _OctreeImageBase
+
+    class Labels(Labels, _OctreeImageBase):
+        pass


Test patch:
Repository: napari/napari

Issue:
Showing notifications needs to be more robust to an existing QtApp
## 🐛 Bug

In `qt_event_loop.py`, we chose to put this connection of the notification manager inside of the conditional that only runs if napari created the app:

```
        notification_manager.notification_ready.connect(
            NapariQtNotification.show_notification
        )
        notification_manager.notification_ready.connect(
            show_console_notification
        )
```

The assumption was that otherwise, it's probably an IPython app, and we don't want them to fight for showing tracebacks.  However, if anything else (like magicgui) has instantiated the QApp, then no notifications will show... which is problematic:

https://github.com/jni/affinder/issues/29

so we need to make that conditional a little more robust, and probably configurable via settings


Discussion:

Resolving patch:
diff --git a/napari/_qt/qt_event_loop.py b/napari/_qt/qt_event_loop.py
--- a/napari/_qt/qt_event_loop.py
+++ b/napari/_qt/qt_event_loop.py
@@ -150,6 +150,7 @@ def get_app(
         app.setOrganizationDomain(kwargs.get('org_domain'))
         set_app_id(kwargs.get('app_id'))
 
+    if not _ipython_has_eventloop():
         notification_manager.notification_ready.connect(
             NapariQtNotification.show_notification
         )


Test patch:
Repository: napari/napari

Issue:
Tooltips not using field description in preferences
## 🐛 Bug

`Field.description` attributes in settings aren't being used for tooltips.  For example, all widgets in the application section just use the docstring of  `ApplicationSettings`

<img width="423" alt="Untitled" src="https://user-images.githubusercontent.com/1609449/115116483-b0b48300-9f67-11eb-801e-21eb44b4fdce.png">

## Expected behavior

tooltips for each field uses the `Field(description=...)` when available


Discussion:

Resolving patch:
diff --git a/napari/_vendor/qt_json_builder/qt_jsonschema_form/form.py b/napari/_vendor/qt_json_builder/qt_jsonschema_form/form.py
--- a/napari/_vendor/qt_json_builder/qt_jsonschema_form/form.py
+++ b/napari/_vendor/qt_json_builder/qt_jsonschema_form/form.py
@@ -99,7 +99,7 @@ def validate(data):
         return form
 
     def create_widget(
-        self, schema: dict, ui_schema: dict, state=None, description = "",
+        self, schema: dict, ui_schema: dict, state=None, description: str = "",
     ) -> widgets.SchemaWidgetMixin:
         schema_type = get_schema_type(schema)
 
@@ -116,8 +116,12 @@ def create_widget(
         widget_variant = ui_schema.get('ui:widget', default_variant)
         widget_cls = self.widget_map[schema_type][widget_variant]
         widget = widget_cls(schema, ui_schema, self)
-        widget.setDescription(description)
         default_state = get_widget_state(schema, state)
         if default_state is not None:
             widget.state = default_state
+
+        if description:
+            widget.setDescription(description)
+            widget.setToolTip(description)
+
         return widget
diff --git a/napari/_vendor/qt_json_builder/qt_jsonschema_form/widgets.py b/napari/_vendor/qt_json_builder/qt_jsonschema_form/widgets.py
--- a/napari/_vendor/qt_json_builder/qt_jsonschema_form/widgets.py
+++ b/napari/_vendor/qt_json_builder/qt_jsonschema_form/widgets.py
@@ -212,8 +212,8 @@ def configure(self):
 
         self.setRange(minimum, maximum)
 
-        def setDescription(self, description: str):
-            self.description = description
+    def setDescription(self, description: str):
+        self.description = description
 
 
 class QColorButton(QtWidgets.QPushButton):


Test patch:
Repository: napari/napari

Issue:
Uninstallation feedback
## 🐛 Bug
#### Description

I was surprised that "Removing" a plugin from napari uninstalled it from my environment.

#### Steps/Code to Reproduce

 * Start napari
 * open "Plugins" > "Install/Uninstall Packages"
 * find ome-zarr under "Installed Plugins"
 * click on "remove"

#### Expected Results

I perhaps naively assumed this would just remove the plugin from the current context.

#### Actual Results

The package was _uninstalled_.



Discussion:
that window is a gui-based interface for pip (which is why we named the menu "Install/Uninstall Packages").  If you want to have napari ignore a plugin (but not uninstall it) then use the checkbox next to the name (rather than the remove button):

![Screen Shot 2021-05-27 at 10 05 27 AM](https://user-images.githubusercontent.com/1609449/119840544-17b44880-bed3-11eb-82c8-bdda72b6d801.png)

(this requires #2565)

would changing the button text from "remove" to "uninstall" make this clearer?
Understood, and definitely no suggestion that that be changed. Just a datapoint that someone who understands these things quite well was *still* surprised. Minimally, I'd suggest renaming "remove" to "uninstall" to match the menu item. I'll leave people who understand UI/UX to say whether or not some other form of feedback ("package uninstalled", etc.) would prevent confusion.

Resolving patch:
diff --git a/napari/_qt/dialogs/qt_plugin_dialog.py b/napari/_qt/dialogs/qt_plugin_dialog.py
--- a/napari/_qt/dialogs/qt_plugin_dialog.py
+++ b/napari/_qt/dialogs/qt_plugin_dialog.py
@@ -121,7 +121,7 @@ def __init__(
             self.package_name.setText(f"{package_name} {version}")
             self.summary.setText(summary)
             self.package_author.setText(author)
-            self.action_button.setText(trans._("remove"))
+            self.action_button.setText(trans._("uninstall"))
             self.action_button.setObjectName("remove_button")
             self.enabled_checkbox.setChecked(enabled)
             if PluginError.get(plugin_name=plugin_name):


Test patch:
Repository: napari/napari

Issue:
Paint cursor disappears if scale is negative
## 🐛 Bug

When using a negative scale (which is our hack to flip axes and actually occurs in some .nd2 files we're using from collaborators), the painting and eraser cursors are not drawn on the canvas.

## To Reproduce

```python
import numpy as np
import napari

viewer = napari.Viewer()
layer = viewer.add_labels(np.zeros((512, 512), dtype=int), scale=[-1, -1])
layer.mode = 'paint'

napari.run()
```

Then move the cursor over the canvas.



Discussion:

Resolving patch:
diff --git a/napari/layers/labels/labels.py b/napari/layers/labels/labels.py
--- a/napari/layers/labels/labels.py
+++ b/napari/layers/labels/labels.py
@@ -339,7 +339,7 @@ def brush_size(self, brush_size):
         data2world_scale = np.mean(
             [self.scale[d] for d in self._dims_displayed]
         )
-        self.cursor_size = self.brush_size * data2world_scale
+        self.cursor_size = abs(self.brush_size * data2world_scale)
         self.events.brush_size()
 
     @property
@@ -657,7 +657,7 @@ def mode(self, mode: Union[str, Mode]):
             data2world_scale = np.mean(
                 [self.scale[d] for d in self._dims_displayed]
             )
-            self.cursor_size = self.brush_size * data2world_scale
+            self.cursor_size = abs(self.brush_size * data2world_scale)
             self.interactive = False
             self.help = trans._(
                 'hold <space> to pan/zoom, hold <shift> to toggle preserve_labels, hold <control> to fill, hold <alt> to erase, drag to paint a label'
@@ -677,7 +677,7 @@ def mode(self, mode: Union[str, Mode]):
             data2world_scale = np.mean(
                 [self.scale[d] for d in self._dims_displayed]
             )
-            self.cursor_size = self.brush_size * data2world_scale
+            self.cursor_size = abs(self.brush_size * data2world_scale)
             self.interactive = False
             self.help = trans._(
                 'hold <space> to pan/zoom, drag to erase a label'


Test patch:
diff --git a/napari/layers/labels/_tests/test_labels.py b/napari/layers/labels/_tests/test_labels.py
--- a/napari/layers/labels/_tests/test_labels.py
+++ b/napari/layers/labels/_tests/test_labels.py
@@ -878,3 +878,9 @@ def test_switching_display_func():
     label_data = np.random.randint(0, 5, size=(50, 50))
     layer = Labels(label_data)
     assert layer._color_lookup_func == layer._lookup_with_index
+
+
+def test_cursor_size_with_negative_scale():
+    layer = Labels(np.zeros((5, 5), dtype=int), scale=[-1, -1])
+    layer.mode = 'paint'
+    assert layer.cursor_size > 0
Repository: napari/napari

Issue:
Track layer track_id dtype overflow
## 🐛 Bug

<!-- A clear and concise description of what the bug is. -->

When number of created tracks bigger than 2^16, rendering goes little crazy.  Tracks rendered after id 2^16 crossing the entire image instead of appearing as limited size vectors. 

Editing the following place in the codebase can help us to avoid this problem:
https://github.com/napari/napari/blob/2107d3194b360a6a5bf82fc85ff01e31d965ff7e/napari/layers/tracks/_track_utils.py#L185

I am wondering if there is anything else I should be aware of before opening a PR to change track_id dtype to `uint32` or `uint64`? @jni @sofroniewn @kevinyamauchi 

## To Reproduce

Steps to reproduce the behavior:

1. Run the following script

```
import napari
import numpy as np

np.random.seed(1)
tracks = []

with napari.gui_qt():

    for i in range(2**16 + 1):
       z = np.random.randint(50)
       y = np.random.randint(2048)
       x = np.random.randint(2048)
       tracks += [[i,0,z,y,x],[i,1,z,y+10,x+10]]

    napari.view_tracks(tracks)
```

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->

## Expected behavior

<!-- A clear and concise description of what you expected to happen. -->

Render all tracks as (10, 10) vectors no matter what is the number of tracks.

## Environment

napari: 0.4.8
Platform: macOS-10.14.6-x86_64-i386-64bit
System: MacOS 10.14.6
Python: 3.8.10 (default, May 19 2021, 11:01:55) [Clang 10.0.0 ]
Qt: 5.15.2
PyQt5: 5.15.4
NumPy: 1.20.3
SciPy: 1.6.3
Dask: 2021.05.0
VisPy: 0.6.6

OpenGL:
- GL version: 2.1 ATI-2.11.23
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 1680x1050, scale 2.0
- screen 2: resolution 1920x1080, scale 2.0

Plugins:
- console: 0.0.3
- svg: 0.1.5

## Additional context

<!-- Add any other context about the problem here. -->



Discussion:

Resolving patch:
diff --git a/napari/layers/tracks/_track_utils.py b/napari/layers/tracks/_track_utils.py
--- a/napari/layers/tracks/_track_utils.py
+++ b/napari/layers/tracks/_track_utils.py
@@ -182,7 +182,7 @@ def graph(self, graph: Dict[int, Union[int, List[int]]]):
     @property
     def track_ids(self):
         """return the track identifiers"""
-        return self.data[:, 0].astype(np.uint16)
+        return self.data[:, 0].astype(np.uint32)
 
     @property
     def unique_track_ids(self):


Test patch:
Repository: napari/napari

Issue:
Axes visual not updating when adding image data
## 🐛 Bug

The axes visual does not automatically stay in sync when image data is added

A similar issue was posted on the [zulip](https://napari.zulipchat.com/#narrow/stream/212875-general/topic/issues.20with.20axes) by Uwe Schmidt, it looks like there might be more to his issue than just this though?

## To Reproduce
```python
import napari
import numpy as np

data = np.random.random((10, 20, 30))

with napari.gui_qt():
    viewer = napari.Viewer()
    viewer.dims.ndisplay = 3
    
    viewer.axes.visible = True
    viewer.add_image(data)
    viewer.camera.angles = (10, -30, 60)
    viewer.screenshot('axes_before_add_layer.png')
```
<img src="https://user-images.githubusercontent.com/7307488/112751330-47270180-8fc5-11eb-8a96-4c4afeb9310f.png" width=300>

Toggling axes on and off fixes the display/labels
```python
    viewer.axes.visible = False
    viewer.axes.visible = True
    viewer.screenshot('axes_after_add_layer.png')
```
<img src="https://user-images.githubusercontent.com/7307488/112751333-49895b80-8fc5-11eb-8aa8-d9692b0581d1.png" width=300>

adding axes once data is already in place works as expected
```python
with napari.gui_qt():
    viewer = napari.Viewer()
    viewer.dims.ndisplay = 3
    
    
    viewer.add_image(data)
    viewer.axes.visible = True
    viewer.camera.angles = (10, -30, 60)
    viewer.screenshot('axes_after_add_layer_only.png')
```

<img src="https://user-images.githubusercontent.com/7307488/112751336-4bebb580-8fc5-11eb-89a0-63ca29346f3c.png" width=300>


## Expected behavior

Axes visual should update itself when data is added to the viewer





Discussion:
Looks like we might be missing some event connection to the axes visual https://github.com/napari/napari/blob/fee29a32b05499466cd2f6e9192cb0c43c92011f/napari/_vispy/vispy_axes_visual.py#L194-L203. Maybe if `ndim` changes? I'd need to look to see exactly how best to do this. We could always fall back on just updating it whenever the layer list changes, but that seems like overkill. the necessary information should be in our dims model

Resolving patch:
diff --git a/napari/_vispy/vispy_axes_visual.py b/napari/_vispy/vispy_axes_visual.py
--- a/napari/_vispy/vispy_axes_visual.py
+++ b/napari/_vispy/vispy_axes_visual.py
@@ -221,6 +221,7 @@ def set_none():
         self._viewer.axes.events.labels.connect(self._on_data_change)
         self._viewer.axes.events.arrows.connect(self._on_data_change)
         self._viewer.dims.events.order.connect(self._on_data_change)
+        self._viewer.dims.events.range.connect(self._on_data_change)
         self._viewer.dims.events.ndisplay.connect(self._on_data_change)
         self._viewer.dims.events.axis_labels.connect(self._on_data_change)
         self._viewer.camera.events.zoom.connect(self._on_zoom_change)


Test patch: