Repository: napari/napari

Issue:
Custom layer does not show up in layer list
## 🐛 Bug

I'm trying to create some custom layers that add functionality to existing napari layers. When I add them to the viewer, they don't show up in the layer list. But, they have been added as the layer controls are there, and I can also select, hide, etc. in the layer list. They are just not shown.

## To Reproduce

Running the following code shows the bug for me:
```python
import numpy as np
import napari

class NewLabels(napari.layers.Labels):
    """'Empty' extension of napari Labels layer."""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

# Make a viewer and add the custom layer
viewer = napari.Viewer()
viewer.add_layer(NewLabels(np.zeros((10, 10, 10), dtype=np.uint8)))
```

## Expected behavior
I'd expect the `NewLabels` layer to show up in the layer list as a Labels layer.

## Environment

I'm using Python 3.7.4 on Windows 10 and napari 0.4.8. I'm running in a jupyter notebook.

## Additional context
In the console where I started jupyter lab, I get the following error message when I try to add the layer:
```
Traceback (most recent call last):
  File "D:\Anaconda3\lib\site-packages\napari\_qt\containers\_layer_delegate.py", line 81, in paint
    self.get_layer_icon(option, index)
  File "D:\Anaconda3\lib\site-packages\napari\_qt\containers\_layer_delegate.py", line 96, in get_layer_icon
    icon = QColoredSVGIcon.from_resources(icon_name)
  File "D:\Anaconda3\lib\site-packages\napari\_qt\qt_resources\_svg.py", line 110, in from_resources
    path = get_icon_path(icon_name)
  File "D:\Anaconda3\lib\site-packages\napari\resources\_icons.py", line 20, in get_icon_path
    icons=set(ICONS),
ValueError: unrecognized icon name: 'new_newlabels'. Known names: {'fill', 'chevron_down', 'polygon', 'check', 'paint', 'long_right_arrow', 'delete_shape', 'rectangle', 'step_right', 'new_points', 'add', 'pop_out', 'chevron_up', 'new_surface', 'minus', 'logo_silhouette', 'copy_to_clipboard', 'left_arrow', 'visibility', 'home', 'direct', 'zoom', 'chevron_left', 'new_image', 'select', 'shuffle', '3D', 'plus', 'long_left_arrow', 'delete', 'picker', 'move_front', 'transpose', 'roll', 'down_arrow', 'right_arrow', 'step_left', 'move_back', 'vertex_insert', 'new_vectors', 'ellipse', 'circle', 'square', 'console', '2D', 'visibility_off', 'warning', 'line', 'info', 'path', 'vertex_remove', 'drop_down', 'new_labels', 'up_arrow', 'new_tracks', 'new_shapes', 'grid', 'erase'}
``´



Discussion:
thanks!  will get on it asap

Resolving patch:
diff --git a/napari/_qt/containers/_layer_delegate.py b/napari/_qt/containers/_layer_delegate.py
--- a/napari/_qt/containers/_layer_delegate.py
+++ b/napari/_qt/containers/_layer_delegate.py
@@ -93,7 +93,10 @@ def get_layer_icon(self, option, index):
         else:
             icon_name = f'new_{layer._type_string}'
 
-        icon = QColoredSVGIcon.from_resources(icon_name)
+        try:
+            icon = QColoredSVGIcon.from_resources(icon_name)
+        except ValueError:
+            return
         # guessing theme rather than passing it through.
         bg = option.palette.color(option.palette.Background).red()
         option.icon = icon.colored(theme='dark' if bg < 128 else 'light')


Test patch:
diff --git a/napari/_tests/test_adding_removing.py b/napari/_tests/test_adding_removing.py
--- a/napari/_tests/test_adding_removing.py
+++ b/napari/_tests/test_adding_removing.py
@@ -1,14 +1,12 @@
-import os
-
 import numpy as np
 import pytest
 
-from napari._tests.utils import layer_test_data
+from napari._tests.utils import layer_test_data, skip_local_popups
 from napari.layers import Image
 from napari.utils.events.event import WarningEmitter
 
 
-@pytest.mark.skipif(bool(not os.getenv("CI")), reason='shows viewer')
+@skip_local_popups
 @pytest.mark.parametrize('Layer, data, _', layer_test_data)
 def test_add_all_layers(make_napari_viewer, Layer, data, _):
     """Make sure that all layers can show in the viewer."""
diff --git a/napari/_tests/test_draw.py b/napari/_tests/test_draw.py
--- a/napari/_tests/test_draw.py
+++ b/napari/_tests/test_draw.py
@@ -1,13 +1,9 @@
-import os
 import sys
 
 import numpy as np
 import pytest
 
-skip_local_popups = pytest.mark.skipif(
-    not os.getenv('CI') and os.getenv('NAPARI_POPUP_TESTS', '0') == '0',
-    reason='Tests requiring GUI windows are skipped locally by default.',
-)
+from napari._tests.utils import skip_local_popups
 
 
 @skip_local_popups
diff --git a/napari/_tests/test_viewer.py b/napari/_tests/test_viewer.py
--- a/napari/_tests/test_viewer.py
+++ b/napari/_tests/test_viewer.py
@@ -9,6 +9,7 @@
     check_view_transform_consistency,
     check_viewer_functioning,
     layer_test_data,
+    skip_local_popups,
 )
 from napari.utils._tests.test_naming import eval_with_filename
 
@@ -280,3 +281,15 @@ def test_deleting_points(make_napari_viewer):
     pts_layer.remove_selected()
 
     assert len(pts_layer.data) == 3
+
+
+@skip_local_popups
+def test_custom_layer(make_napari_viewer):
+    """Make sure that custom layers subclasses can be added to the viewer."""
+
+    class NewLabels(layers.Labels):
+        """'Empty' extension of napari Labels layer."""
+
+    # Make a viewer and add the custom layer
+    viewer = make_napari_viewer(show=True)
+    viewer.add_layer(NewLabels(np.zeros((10, 10, 10), dtype=np.uint8)))
diff --git a/napari/_tests/test_with_screenshot.py b/napari/_tests/test_with_screenshot.py
--- a/napari/_tests/test_with_screenshot.py
+++ b/napari/_tests/test_with_screenshot.py
@@ -1,10 +1,9 @@
 import collections
-import os
-import sys
 
 import numpy as np
 import pytest
 
+from napari._tests.utils import skip_local_popups, skip_on_win_ci
 from napari.utils.interactions import (
     ReadOnlyWrapper,
     mouse_move_callbacks,
@@ -12,15 +11,6 @@
     mouse_release_callbacks,
 )
 
-skip_on_win_ci = pytest.mark.skipif(
-    sys.platform.startswith('win') and os.getenv('CI', '0') != '0',
-    reason='Screenshot tests are not supported on windows CI.',
-)
-skip_local_popups = pytest.mark.skipif(
-    not os.getenv('CI') and os.getenv('NAPARI_POPUP_TESTS', '0') == '0',
-    reason='Tests requiring GUI windows are skipped locally by default.',
-)
-
 
 @skip_on_win_ci
 @skip_local_popups
diff --git a/napari/_tests/utils.py b/napari/_tests/utils.py
--- a/napari/_tests/utils.py
+++ b/napari/_tests/utils.py
@@ -1,4 +1,8 @@
+import os
+import sys
+
 import numpy as np
+import pytest
 
 from napari import Viewer
 from napari.layers import (
@@ -11,6 +15,17 @@
     Vectors,
 )
 
+skip_on_win_ci = pytest.mark.skipif(
+    sys.platform.startswith('win') and os.getenv('CI', '0') != '0',
+    reason='Screenshot tests are not supported on windows CI.',
+)
+
+skip_local_popups = pytest.mark.skipif(
+    not os.getenv('CI') and os.getenv('NAPARI_POPUP_TESTS', '0') == '0',
+    reason='Tests requiring GUI windows are skipped locally by default.',
+)
+
+
 """
 Used as pytest params for testing layer add and view functionality (Layer class, data, ndim)
 """
diff --git a/napari/_vispy/_tests/test_image_rendering.py b/napari/_vispy/_tests/test_image_rendering.py
--- a/napari/_vispy/_tests/test_image_rendering.py
+++ b/napari/_vispy/_tests/test_image_rendering.py
@@ -1,8 +1,6 @@
-import os
-import sys
-
 import numpy as np
-import pytest
+
+from napari._tests.utils import skip_on_win_ci
 
 
 def test_image_rendering(make_napari_viewer):
@@ -41,10 +39,7 @@ def test_image_rendering(make_napari_viewer):
     assert layer.rendering == 'additive'
 
 
-@pytest.mark.skipif(
-    sys.platform.startswith('win') or not os.getenv("CI"),
-    reason='Screenshot tests are not supported on napari windows CI.',
-)
+@skip_on_win_ci
 def test_visibility_consistency(qtbot, make_napari_viewer):
     """Make sure toggling visibility maintains image contrast.
 
diff --git a/napari/_vispy/_tests/test_vispy_multiscale.py b/napari/_vispy/_tests/test_vispy_multiscale.py
--- a/napari/_vispy/_tests/test_vispy_multiscale.py
+++ b/napari/_vispy/_tests/test_vispy_multiscale.py
@@ -1,17 +1,6 @@
-import os
-import sys
-
 import numpy as np
-import pytest
-
-skip_on_win_ci = pytest.mark.skipif(
-    sys.platform.startswith('win') and os.getenv('CI', '0') != '0',
-    reason='Screenshot tests are not supported on windows CI.',
-)
-skip_local_popups = pytest.mark.skipif(
-    not os.getenv('CI') and os.getenv('NAPARI_POPUP_TESTS', '0') == '0',
-    reason='Tests requiring GUI windows are skipped locally by default.',
-)
+
+from napari._tests.utils import skip_local_popups, skip_on_win_ci
 
 
 def test_multiscale(make_napari_viewer):
diff --git a/napari/_vispy/experimental/_tests/test_vispy_tiled_image.py b/napari/_vispy/experimental/_tests/test_vispy_tiled_image.py
--- a/napari/_vispy/experimental/_tests/test_vispy_tiled_image.py
+++ b/napari/_vispy/experimental/_tests/test_vispy_tiled_image.py
@@ -1,22 +1,11 @@
-import os
-import sys
-
 import numpy as np
 import pytest
 
+from napari._tests.utils import skip_local_popups, skip_on_win_ci
 from napari._vispy.experimental.vispy_tiled_image_layer import (
     VispyTiledImageLayer,
 )
 
-skip_on_win_ci = pytest.mark.skipif(
-    sys.platform.startswith('win') and os.getenv('CI', '0') != '0',
-    reason='Screenshot tests are not supported on windows CI.',
-)
-skip_local_popups = pytest.mark.skipif(
-    not os.getenv('CI') and os.getenv('NAPARI_POPUP_TESTS', '0') == '0',
-    reason='Tests requiring GUI windows are skipped locally by default.',
-)
-
 # Add a loading delay in ms.
 SHORT_LOADING_DELAY = 1
 LONG_LOADING_DELAY = 250