def test_scale(): """Test instantiating anisotropic 3D volume.""" shape = (10, 15, 20) scale = [3, 1, 1] full_shape = tuple(np.multiply(shape, scale)) np.random.seed(0) data = np.random.random(shape) layer = Image(data, scale=scale) layer.dims.ndisplay = 3 assert np.all(layer.data == data) assert layer.ndim == len(shape) assert layer.shape == full_shape # Note that the scale appears as the step size in the range assert layer.dims.range == list( (0, m, s) for m, s in zip(full_shape, scale)) assert layer._data_view.shape == shape[-3:]
class Image3DSuite: """Benchmarks for the Image layer with 3D data.""" params = [2**i for i in range(4, 11)] def setup(self, n): if "CI" in os.environ and n > 512: raise NotImplementedError("Skip on CI (not enough memory)") np.random.seed(0) self.data = np.random.random((n, n, n)) self.new_data = np.random.random((n, n, n)) self.layer = Image(self.data) def time_create_layer(self, n): """Time to create an image layer.""" Image(self.data) def time_set_view_slice(self, n): """Time to set view slice.""" self.layer._set_view_slice() def time_update_thumbnail(self, n): """Time to update thumbnail.""" self.layer._update_thumbnail() def time_get_value(self, n): """Time to get current value.""" self.layer.get_value((0, ) * 3) def time_set_data(self, n): """Time to get current value.""" self.layer.data = self.new_data def time_refresh(self, n): """Time to refresh view.""" self.layer.refresh() def mem_layer(self, n): """Memory used by layer.""" return Image(self.data) def mem_data(self, n): """Memory used by raw data.""" return self.data
def test_stack_to_images_multiscale(): """Test that a 3 channel multiscale image returns 3 multiscale images.""" data = list() data.append(np.random.randint(0, 200, (3, 128, 128))) data.append(np.random.randint(0, 200, (3, 64, 64))) data.append(np.random.randint(0, 200, (3, 32, 32))) data.append(np.random.randint(0, 200, (3, 16, 16))) stack = Image(data) images = stack_to_images(stack, 0) assert len(images) == 3 assert len(images[0].data) == 4 assert images[0].data[-1].shape[-1] == 16 assert images[1].data[-1].shape[-1] == 16 assert images[2].data[-1].shape[-1] == 16
def test_clim_slider_step_size_and_precision(qtbot, mag): """Make sure the slider has a reasonable step size and precision. ...across a broad range of orders of magnitude. """ layer = Image(np.random.rand(20, 20) / 10**mag) popup = create_range_popup(layer, 'contrast_limits') qtbot.addWidget(popup) # the range slider popup labels should have a number of decimal points that # is inversely proportional to the order of magnitude of the range of data, # but should never be greater than 5 or less than 0 assert popup.precision == max(min(mag + 3, 5), 0) # the slider step size should also be inversely proportional to the data # range, with 1000 steps across the data range assert np.ceil(popup.slider._step * 10**(mag + 4)) == 10
def test_corner_value(): """Test getting the value of the data at the new position.""" shapes = [(40, 20), (20, 10), (10, 5)] np.random.seed(0) data = [np.random.random(s) for s in shapes] layer = Image(data, multiscale=True) value = layer.get_value((0,) * 2) target_position = (39, 19) target_level = 0 layer.data_level = target_level layer.corner_pixels[1] = shapes[target_level] # update requested view layer.refresh() # Test position at corner of image value = layer.get_value(target_position) np.testing.assert_allclose( value, (target_level, data[target_level][target_position]) ) # Test position at outside image value = layer.get_value((40, 20)) assert value[1] is None
def test_images_to_stack_none_scale(): """Test combining images using scale & translate from 1st image in list""" images = [ Image( np.random.randint(0, 255, (10, 128, 128)), scale=(4, 1, 1), translate=(0, -1, 2), ) for _ in range(3) ] stack = images_to_stack(images, 1, colormap='green') assert isinstance(stack, Image) assert stack.data.shape == (10, 3, 128, 128) assert stack.colormap.name == 'green' assert list(stack.scale) == [4, 1, 1, 1] assert list(stack.translate) == [0, 0, -1, 2]
def layers(): """Fixture that supplies a layers list for testing. Returns ------- napari.components.LayerList The desired napari LayerList. """ np.random.seed(0) list_of_layers = [ Image(np.random.rand(20, 20)), Labels(np.random.randint(10, size=(20, 2))), Points(np.random.rand(20, 2)), Shapes(np.random.rand(10, 2, 2)), Vectors(np.random.rand(10, 2, 2)), ] return LayerList(list_of_layers)
def test_instantiate_with_experimental_clipping_planes_dict(): planes = [ { 'position': (0, 0, 0), 'normal': (0, 0, 1) }, { 'position': (0, 1, 0), 'normal': (1, 0, 0) }, ] image = Image(np.ones((32, 32, 32)), experimental_clipping_planes=planes) for i in range(len(planes)): assert (image.experimental_clipping_planes[i].position == planes[i] ['position']) assert (image.experimental_clipping_planes[i].normal == planes[i] ['normal'])
def test_images_to_stack_with_scale(): """Test that 3-Image list is combined to stack with scale and translate.""" images = [ Image(np.random.randint(0, 255, (10, 128, 128))) for _ in range(3) ] stack = images_to_stack(images, 1, colormap='green', scale=(3, 1, 1, 1), translate=(1, 0, 2, 3)) assert isinstance(stack, Image) assert stack.data.shape == (10, 3, 128, 128) assert stack.colormap.name == 'green' assert list(stack.scale) == [3, 1, 1, 1] assert list(stack.translate) == [1, 0, 2, 3]
def test_plane_controls_show_hide_on_depiction_change(qtbot): """Changing depiction mode should show/hide plane controls in 3D.""" layer = Image(np.random.rand(10, 15, 20)) layer._slice_dims(ndisplay=3) qtctrl = QtImageControls(layer) qtbot.addWidget(qtctrl) layer.depiction = 'volume' assert qtctrl.planeControls.isHidden() layer.depiction = 'plane' assert not qtctrl.planeControls.isHidden() # isVisible() != not isHidden()
def test_clim_slider_step_size_and_precision(qtbot, mag): """Make sure the slider has a reasonable step size and precision. ...across a broad range of orders of magnitude. """ layer = Image(np.random.rand(20, 20) * 10**mag) popup = QContrastLimitsPopup(layer) qtbot.addWidget(popup) # the range slider popup labels should have a number of decimal points that # is inversely proportional to the order of magnitude of the range of data, # but should never be greater than 5 or less than 0 decimals = min(6, max(int(3 - mag), 0)) assert popup.slider.decimals() == decimals # the slider step size should also be inversely proportional to the data # range, with 1000 steps across the data range assert popup.slider.singleStep() == 10**-decimals
def test_clearing_layerlist(qtbot): """Test clearing layer list.""" layers = LayerList() view = QtLayerList(layers) qtbot.addWidget(view) layers.extend([Image(np.random.random((15, 15))) for _ in range(4)]) assert view.vbox_layout.count() == 2 * (len(layers) + 1) assert check_layout_layers(view.vbox_layout, layers) assert check_layout_dividers(view.vbox_layout, len(layers)) layers.clear() assert len(layers) == 0 assert view.vbox_layout.count() == 2 * (len(layers) + 1) assert check_layout_layers(view.vbox_layout, layers) assert check_layout_dividers(view.vbox_layout, len(layers))
class Image2DSuite: """Benchmarks for the Image layer with 2D data.""" params = [2**i for i in range(4, 13)] def setup(self, n): np.random.seed(0) self.data = np.random.random((n, n)) self.new_data = np.random.random((n, n)) self.layer = Image(self.data) def time_create_layer(self, n): """Time to create an image layer.""" Image(self.data) def time_set_view_slice(self, n): """Time to set view slice.""" self.layer._set_view_slice() def time_update_thumbnail(self, n): """Time to update thumbnail.""" self.layer._update_thumbnail() def time_get_value(self, n): """Time to get current value.""" self.layer.get_value() def time_set_data(self, n): """Time to get current value.""" self.layer.data = self.new_data def time_refresh(self, n): """Time to refresh view.""" self.layer.refresh() def mem_layer(self, n): """Memory used by layer.""" return self.layer def mem_data(self, n): """Memory used by raw data.""" return self.data
def test_multiscale_tuple(): """Test instantiating Image layer multiscale tuple.""" shape = (40, 20) np.random.seed(0) img = np.random.random(shape) if skimage.__version__ > '0.19': pyramid_kwargs = {'channel_axis': None} else: pyramid_kwargs = {'multichannel': False} data = list(pyramid_gaussian(img, **pyramid_kwargs)) layer = Image(data) assert layer.data == data assert layer.multiscale is True assert layer.ndim == len(shape) np.testing.assert_array_equal(layer.extent.data[1], shape) assert layer.rgb is False assert layer._data_view.ndim == 2
def test_plane_controls_show_hide_on_ndisplay_change(qtbot): """Changing ndisplay should show/hide plane controls if depicting a plane.""" layer = Image(np.random.rand(10, 15, 20)) qtctrl = QtImageControls(layer) qtbot.addWidget(qtctrl) layer._slice_dims(ndisplay=3) layer.depiction = 'plane' assert not qtctrl.planeThicknessSlider.isHidden() assert not qtctrl.planeThicknessLabel.isHidden() assert not qtctrl.planeNormalButtons.isHidden() assert not qtctrl.planeNormalLabel.isHidden() layer._slice_dims(ndisplay=2) assert qtctrl.planeThicknessSlider.isHidden() assert qtctrl.planeThicknessLabel.isHidden() assert qtctrl.planeNormalButtons.isHidden() assert qtctrl.planeNormalLabel.isHidden()
def test_opacity(): """Test setting layer opacity.""" np.random.seed(0) data = np.random.random((10, 15)) layer = Image(data) assert layer.opacity == 1 layer.opacity = 0.5 assert layer.opacity == 0.5 layer = Image(data, opacity=0.6) assert layer.opacity == 0.6 layer.opacity = 0.3 assert layer.opacity == 0.3
def test_blending(): """Test setting layer blending.""" np.random.seed(0) data = np.random.random((10, 15)) layer = Image(data) assert layer.blending == 'translucent' layer.blending = 'additive' assert layer.blending == 'additive' layer = Image(data, blending='additive') assert layer.blending == 'additive' layer.blending = 'opaque' assert layer.blending == 'opaque'
def layer(request): """Parameterized fixture that supplies a layer for testing. Parameters ---------- request : _pytest.fixtures.SubRequest The pytest request object Returns ------- napari.layers.Layer The desired napari Layer. """ np.random.seed(0) if request.param == 'image': data = np.random.rand(20, 20) return Image(data) elif request.param == 'labels': data = np.random.randint(10, size=(20, 20)) return Labels(data) elif request.param == 'points': data = np.random.rand(20, 2) return Points(data) elif request.param == 'shapes': data = [ np.random.rand(2, 2), np.random.rand(2, 2), np.random.rand(6, 2), np.random.rand(6, 2), np.random.rand(2, 2), ] shape_type = ['ellipse', 'line', 'path', 'polygon', 'rectangle'] return Shapes(data, shape_type=shape_type) elif request.param == 'shapes-rectangles': data = np.random.rand(7, 4, 2) return Shapes(data) elif request.param == 'vectors': data = np.random.rand(20, 2, 2) return Vectors(data) else: return None
def test_visiblity(): """Test setting layer visiblity.""" np.random.seed(0) data = np.random.random((10, 15)) layer = Image(data) assert layer.visible is True layer.visible = False assert layer.visible is False layer = Image(data, visible=False) assert layer.visible is False layer.visible = True assert layer.visible is True
def test_reordering_layers(qtbot): """ Test reordering layers. """ layers = LayerList() view = QtLayerList(layers) qtbot.addWidget(view) layer_a = Image(np.random.random((10, 10)), name='image_a') layer_b = Image(np.random.random((15, 15)), name='image_b') layer_c = Image(np.random.random((15, 15)), name='image_c') layer_d = Image(np.random.random((15, 15)), name='image_d') layers.append(layer_a) layers.append(layer_b) layers.append(layer_c) layers.append(layer_d) # Check layout and layers list match after rearranging layers layers[:] = [layers[i] for i in (1, 0, 3, 2)] assert view.vbox_layout.count() == 2 * (len(layers) + 1) assert check_layout_layers(view.vbox_layout, layers) assert check_layout_dividers(view.vbox_layout, len(layers)) # Do another reorder and check layout and layers list match # after swapping layers again layers[:] = [layers[i] for i in (1, 0, 3, 2)] assert view.vbox_layout.count() == 2 * (len(layers) + 1) assert check_layout_layers(view.vbox_layout, layers) assert check_layout_dividers(view.vbox_layout, len(layers)) # Check layout and layers list match after reversing list layers.reverse() assert view.vbox_layout.count() == 2 * (len(layers) + 1) assert check_layout_layers(view.vbox_layout, layers) assert check_layout_dividers(view.vbox_layout, len(layers)) # Check layout and layers list match after rearranging selected layers layer_e = Image(np.random.random((15, 15))) layer_f = Image(np.random.random((15, 15))) layers.append(layer_e) layers.append(layer_f) for layer, s in zip(layers, [False, True, False, False, True, False]): layer.selected = s layers.move_selected(1, 2) assert view.vbox_layout.count() == 2 * (len(layers) + 1) assert check_layout_layers(view.vbox_layout, layers) assert check_layout_dividers(view.vbox_layout, len(layers))
def test_clim_slider_step_size_and_precision(qtbot, mag): """Make sure the slider has a reasonable step size and precision. ...across a broad range of orders of magnitude. """ layer = Image(np.random.rand(20, 20) * 10**mag) popup = QContrastLimitsPopup(layer) qtbot.addWidget(popup) # scale precision with the log of the data range order of magnitude # eg. 0 - 1 (0 order of mag) -> 3 decimal places # 0 - 10 (1 order of mag) -> 2 decimals # 0 - 100 (2 orders of mag) -> 1 decimal # ≥ 3 orders of mag -> no decimals # no more than 64 decimals decimals = range_to_decimals(layer.contrast_limits, layer.dtype) assert popup.slider.decimals() == decimals # the slider step size should also be inversely proportional to the data # range, with 1000 steps across the data range assert popup.slider.singleStep() == 10**-decimals
def test_reordering_layers(qtbot): """ Test reordering layers. """ layers = LayerList() view = QtLayerList(layers) qtbot.addWidget(view) layer_a = Image(np.random.random((10, 10)), name='image_a') layer_b = Image(np.random.random((15, 15)), name='image_b') layer_c = Image(np.random.random((15, 15)), name='image_c') layer_d = Image(np.random.random((15, 15)), name='image_d') layers.append(layer_a) layers.append(layer_b) layers.append(layer_c) layers.append(layer_d) # Check layout and layers list match after rearranging layers layers[:] = layers[(1, 0, 3, 2)] assert view.vbox_layout.count() == 2 * (len(layers) + 1) assert check_layout_layers(view.vbox_layout, layers) assert check_layout_dividers(view.vbox_layout, len(layers)) # Check layout and layers list match after swapping two layers layers['image_b', 'image_c'] = layers['image_c', 'image_b'] assert view.vbox_layout.count() == 2 * (len(layers) + 1) assert check_layout_layers(view.vbox_layout, layers) assert check_layout_dividers(view.vbox_layout, len(layers)) # Check layout and layers list match after reversing list # TEST CURRENTLY FAILING # layers.reverse() # assert view.vbox_layout.count() == 2 * (len(layers) + 1) # assert check_layout_layers(view.vbox_layout, layers) # assert check_layout_dividers(view.vbox_layout, len(layers)) # Check layout and layers list match after rearranging selected layers layer_e = Image(np.random.random((15, 15))) layer_f = Image(np.random.random((15, 15))) layers.append(layer_e) layers.append(layer_f) for l, s in zip(layers, [False, True, False, False, True, False]): l.selected = s layers.move_selected(1, 2) assert view.vbox_layout.count() == 2 * (len(layers) + 1) assert check_layout_layers(view.vbox_layout, layers) assert check_layout_dividers(view.vbox_layout, len(layers))
def test_blending(): """Test setting layer blending.""" shapes = [(40, 20), (20, 10), (10, 5)] np.random.seed(0) data = [np.random.random(s) for s in shapes] layer = Image(data, is_pyramid=True) assert layer.blending == 'translucent' layer.blending = 'additive' assert layer.blending == 'additive' layer = Image(data, is_pyramid=True, blending='additive') assert layer.blending == 'additive' layer.blending = 'opaque' assert layer.blending == 'opaque'
def test_opacity(): """Test setting layer opacity.""" shapes = [(40, 20), (20, 10), (10, 5)] np.random.seed(0) data = [np.random.random(s) for s in shapes] layer = Image(data, is_pyramid=True) assert layer.opacity == 1.0 layer.opacity = 0.5 assert layer.opacity == 0.5 layer = Image(data, is_pyramid=True, opacity=0.6) assert layer.opacity == 0.6 layer.opacity = 0.3 assert layer.opacity == 0.3
def test_visiblity(): """Test setting layer visiblity.""" shapes = [(40, 20), (20, 10), (10, 5)] np.random.seed(0) data = [np.random.random(s) for s in shapes] layer = Image(data, is_pyramid=True) assert layer.visible == True layer.visible = False assert layer.visible == False layer = Image(data, is_pyramid=True, visible=False) assert layer.visible == False layer.visible = True assert layer.visible == True
def test_naming(): """ Test unique naming in LayerList """ layers = LayerList() layer_a = Image(np.random.random((10, 10)), name='img') layer_b = Image(np.random.random((15, 15)), name='img') layers.append(layer_a) layers.append(layer_b) assert [lay.name for lay in layers] == ['img', 'img [1]'] layer_b.name = 'chg' assert [lay.name for lay in layers] == ['img', 'chg'] layer_a.name = 'chg' assert [lay.name for lay in layers] == ['chg [1]', 'chg']
def test_depiction_combobox_changes(qtbot): """Changing the model attribute should update the view.""" layer = Image(np.random.rand(10, 15, 20)) layer._slice_dims(ndisplay=3) qtctrl = QtImageControls(layer) qtbot.addWidget(qtctrl) combo_box = qtctrl.depictionComboBox opts = {combo_box.itemText(i) for i in range(combo_box.count())} depiction_options = { 'volume', 'plane', } assert opts == depiction_options layer.depiction = 'plane' assert combo_box.findText('plane') == combo_box.currentIndex() layer.depiction = 'volume' assert combo_box.findText('volume') == combo_box.currentIndex()
def test_adding_removing_layer(make_napari_viewer): """Test adding and removing a layer.""" np.random.seed(0) viewer = make_napari_viewer() # Create layer data = np.random.random((2, 6, 30, 40)) layer = Image(data) # Check that no internal callbacks have been registered assert len(layer.events.callbacks) == 0 for em in layer.events.emitters.values(): assert len(em.callbacks) == 0 # Add layer viewer.layers.append(layer) assert np.all(viewer.layers[0].data == data) assert len(viewer.layers) == 1 assert viewer.dims.ndim == 4 # check that adding a layer created new callbacks assert any(len(em.callbacks) > 0 for em in layer.events.emitters.values()) # Remove layer, viewer resets layer = viewer.layers[0] viewer.layers.remove(layer) assert len(viewer.layers) == 0 assert viewer.dims.ndim == 2 # Check that no other internal callbacks have been registered assert len(layer.events.callbacks) == 0 for em in layer.events.emitters.values(): assert len(em.callbacks) == 0 # re-add layer viewer.layers.append(layer) assert len(viewer.layers) == 1 assert viewer.dims.ndim == 4
def test_write(mock_pm: PluginManager): # saving an image without a writer goes straight to npe2.write # it will use our plugin writer image = Image(np.random.rand(20, 20), name='ex_img') _npe2.write_layers('some_file.tif', [image]) mock_pm.commands.get.assert_called_once_with(f'{PLUGIN_NAME}.my_writer') # points won't trigger our sample writer mock_pm.commands.get.reset_mock() points = Points(np.random.rand(20, 2), name='ex_points') _npe2.write_layers('some_file.tif', [points]) mock_pm.commands.get.assert_not_called() # calling _npe2.write_layers with a specific writer contribution should # directly execute the writer.exec with arguments appropriate for the # writer spec (single or multi-writer) mock_pm.commands.get.reset_mock() writer = mock_pm.get_manifest(PLUGIN_NAME).contributions.writers[0] writer = MagicMock(wraps=writer) writer.exec.return_value = [''] assert _npe2.write_layers('some_file.tif', [points], writer=writer) == [''] mock_pm.commands.get.assert_not_called() writer.exec.assert_called_once() assert writer.exec.call_args_list[0].kwargs['args'][0] == 'some_file.tif'
def test_rendering(): """Test setting rendering.""" np.random.seed(0) data = np.random.random((20, 10, 15)) layer = Image(data) assert layer.rendering == 'mip' # Change rendering property layer.rendering = 'translucent' assert layer.rendering == 'translucent' # Change rendering property layer.rendering = 'attenuated_mip' assert layer.rendering == 'attenuated_mip' # Change rendering property layer.rendering = 'iso' assert layer.rendering == 'iso' # Change rendering property layer.rendering = 'additive' assert layer.rendering == 'additive'