Repository: napari/napari

Issue:
Toggle current layer visibility w/keyboard shortcut
## 🚀 Feature
<!-- A clear and concise description of the feature proposal -->
I'd like to introduce a new keyboard shortcut to toggle visibility of current layer

## Motivation

<!-- Please outline the motivation for the proposal. Is your feature request related to a problem? e.g., I'm always frustrated when [...]. If this is related to another GitHub issue, please link here too -->
When working with a few layers, I often find myself wanting to toggle the visibility of a particular layer to look clearly at one beneath. This has been coming up most recently when painting labels.

## Pitch

<!-- A clear and concise description of what you want to happen. -->
I'd like to propose a new keyboard shortcut to toggle the visibility of the current layer. 

This part is up for debate, but if this is very useful for people, it could be a single key, like `H`, or a combo like `Ctrl+H`, or some other key(s) entirely. 

From looking at the other key bindings, I assume this would be straightforward to do, unless there is some `Qt` issue I'm not familiar with.

## Alternatives

<!-- A clear and concise description of any alternative solutions or features you've considered, if any. -->
At the moment, I use the mouse to manually toggle the visibility.


Discussion:
This seems like a pretty good feature to me @hectormz - you can look here https://github.com/napari/napari/blob/master/napari/_viewer_key_bindings.py#L83-L86 for how we use `Control-A` to select all layers, so seems pretty reasonable to add `Control-H` to toggle visibility for all selected layers (I'd probably go with all selected instead of just the active one)

I'd probably add a method like `toggle_selected_visibility` to our layers_list https://github.com/napari/napari/blob/master/napari/components/layerlist.py#L15 and then just call that from the keybinding as it's nice to keep the keybindings as short as possible.

Curious if @tlambert03 or @jni have an alternative implementation in mind or any other suggestions. 

It would be great if you could make this PR!!
I think this is a great idea, @hectormz !

Do you think it would be also useful to create a "focus layer" keybinding while you're at it? This would hide (or dramatically increase the opacity) of all layers other than the selected layer.
@sofroniewn thanks for the pointers, I'd be happy to put this together!

One question is what to do with homogeneous layers (eg 5 layers selected, with 2 visible and 3 not visible). 
- Should each layer be faithfully toggled?
- Or should it be assumed that the user wants all of them to be invisible/visible, and the state is determined by the majority? (In the example above, all the layers would become visible, and when toggled again, all would become invisible)

@kevinyamauchi I like that idea as well. I'm curious what others think about it, and how it should be implemented. Making all other layers invisible (and faithfully toggling the changed ones back) would be easiest. Is there a layer-level control to change opacity, or would it actually entail going down into each image or shape etc?
You can access the opacity for each layer using the `.opacity` property. Although for the issue you describe here (toggling visibility) it's probably much better to ignore opacity and use only the `.visible` property instead.

Because visibility is a boolean, you could make a "true" toggle by using `not`. I don't know if this is desirable for you, but it does seem like it could be more intuitive than the "majority rules" on/off switch described above?

Here's a short sketch that will let you play with those ideas:
```python
In [1]: import napari

In [2]: from skimage import data

In [3]: viewer = napari.Viewer()

In [4]: viewer.add_image(data.camera(), name='camera')
Out[4]: <Image layer 'camera' at 0x23251d9db50>

In [5]: viewer.add_image(data.astronaut(), name='astronaut')
Out[5]: <Image layer 'astronaut' at 0x23251e00be0>

In [6]: viewer.grid_view()  # let's see them side by side

In [7]: viewer.layers[0].visible = False  # let's manually switch off the visibility for one of these layers

In [8]: viewer.layers.select_all()

In [9]: def toggle_visibility(viewer):
    ...:     for layer in viewer.layers:
    ...:         if layer.selected:
    ...:             layer.visible = not layer.visible
    ...:

In [10]: toggle_visibility(viewer)

In [11]: toggle_visibility(viewer)
```

> Because visibility is a boolean, you could make a "true" toggle by using not. I don't know if this is desirable for you, but it does seem like it could be more intuitive than the "majority rules" on/off switch described above?

That seems like a good place to start. I'd also probably start with a PR just focused on visibility, then there could maybe be a followup for opacity like @kevinyamauchi describes
> I'd also probably start with a PR just focused on visibility, then there could maybe be a followup for opacity like @kevinyamauchi describes

Makes sense to me!
@sofroniewn @kevinyamauchi I'll start with the simpler approach for now.

Thanks @GenevieveBuckley for the sample!

Resolving patch:
diff --git a/napari/_viewer_key_bindings.py b/napari/_viewer_key_bindings.py
--- a/napari/_viewer_key_bindings.py
+++ b/napari/_viewer_key_bindings.py
@@ -140,3 +140,9 @@ def play(viewer):
     else:
         axis = viewer.window.qt_viewer.dims.last_used or 0
         viewer.window.qt_viewer.dims.play(axis)
+
+
+@Viewer.bind_key('V')
+def toggle_selected_visibility(viewer):
+    """Toggle visibility of selected layers"""
+    viewer.layers.toggle_selected_visibility()
diff --git a/napari/components/layerlist.py b/napari/components/layerlist.py
--- a/napari/components/layerlist.py
+++ b/napari/components/layerlist.py
@@ -173,3 +173,9 @@ def select_previous(self, shift=False):
                 self[selected[0] - 1].selected = True
         elif len(self) > 0:
             self[0].selected = True
+
+    def toggle_selected_visibility(self):
+        """Toggle visibility of selected layers"""
+        for layer in self:
+            if layer.selected:
+                layer.visible = not layer.visible


Test patch:
diff --git a/napari/components/_tests/test_layers_list.py b/napari/components/_tests/test_layers_list.py
--- a/napari/components/_tests/test_layers_list.py
+++ b/napari/components/_tests/test_layers_list.py
@@ -393,3 +393,34 @@ def test_move_selected():
         False,
         False,
     ]
+
+
+def test_toggle_visibility():
+    """
+    Test toggling layer visibility
+    """
+    layers = LayerList()
+    layer_a = Image(np.random.random((10, 10)))
+    layer_b = Image(np.random.random((15, 15)))
+    layer_c = Image(np.random.random((15, 15)))
+    layer_d = Image(np.random.random((15, 15)))
+    layers.append(layer_a)
+    layers.append(layer_b)
+    layers.append(layer_c)
+    layers.append(layer_d)
+
+    layers[0].visible = False
+    layers[1].visible = True
+    layers[2].visible = False
+    layers[3].visible = True
+
+    layers.select_all()
+    layers[0].selected = False
+
+    layers.toggle_selected_visibility()
+
+    assert [l.visible for l in layers] == [False, False, True, False]
+
+    layers.toggle_selected_visibility()
+
+    assert [l.visible for l in layers] == [False, True, False, True]