def test_paint(): """Test painting labels with different square brush sizes.""" np.random.seed(0) data = np.random.randint(20, size=(10, 15)) data[:10, :10] = 1 layer = Labels(data) with pytest.warns(FutureWarning): layer.brush_shape = 'square' assert np.unique(layer.data[:5, :5]) == 1 assert np.unique(layer.data[5:10, 5:10]) == 1 layer.brush_size = 9 layer.paint([0, 0], 2) assert np.unique(layer.data[:5, :5]) == 2 assert np.unique(layer.data[5:10, 5:10]) == 1 layer.brush_size = 10 layer.paint([0, 0], 2) assert np.unique(layer.data[:6, :6]) == 2 assert np.unique(layer.data[6:10, 6:10]) == 1 layer.brush_size = 19 layer.paint([0, 0], 2) assert np.unique(layer.data[:5, :5]) == 2 assert np.unique(layer.data[5:10, 5:10]) == 2
def test_paint_2d(brush_shape, expected_sum): """Test painting labels with circle/square brush.""" data = np.zeros((40, 40)) layer = Labels(data) layer.brush_size = 12 layer.brush_shape = brush_shape layer.mode = 'paint' layer.paint((0, 0), 3) layer.brush_size = 12 layer.paint((15, 8), 4) layer.brush_size = 13 layer.paint((30.2, 7.8), 5) layer.brush_size = 12 layer.paint((39, 39), 6) layer.brush_size = 20 layer.paint((15, 27), 7) assert np.sum(layer.data[:8, :8] == 3) == expected_sum[0] assert np.sum(layer.data[9:22, 2:15] == 4) == expected_sum[1] assert np.sum(layer.data[24:37, 2:15] == 5) == expected_sum[2] assert np.sum(layer.data[33:, 33:] == 6) == expected_sum[3] assert np.sum(layer.data[5:26, 17:38] == 7) == expected_sum[4]
def test_paint(): """Test painting labels with different circle brush sizes.""" np.random.seed(0) data = np.random.randint(20, size=(10, 15)) data[:10, :10] = 1 layer = Labels(data) assert np.unique(layer.data[:5, :5]) == 1 assert np.unique(layer.data[5:10, 5:10]) == 1 layer.brush_size = 9 layer.paint([0, 0], 2) assert np.unique(layer.data[:4, :4]) == 2 assert np.unique(layer.data[5:10, 5:10]) == 1 layer.brush_size = 10 layer.paint([0, 0], 2) assert np.unique(layer.data[0:6, 0:3]) == 2 assert np.unique(layer.data[0:3, 0:6]) == 2 assert np.unique(layer.data[6:10, 6:10]) == 1 layer.brush_size = 19 layer.paint([0, 0], 2) assert np.unique(layer.data[0:4, 0:10]) == 2 assert np.unique(layer.data[0:10, 0:4]) == 2 assert np.unique(layer.data[3:7, 3:7]) == 2 assert np.unique(layer.data[7:10, 7:10]) == 1
def test_paint_2d(): """Test painting labels with circle brush.""" data = np.zeros((40, 40), dtype=np.uint32) layer = Labels(data) layer.brush_size = 12 layer.mode = 'paint' layer.paint((0, 0), 3) layer.brush_size = 12 layer.paint((15, 8), 4) layer.brush_size = 13 layer.paint((30.2, 7.8), 5) layer.brush_size = 12 layer.paint((39, 39), 6) layer.brush_size = 20 layer.paint((15, 27), 7) assert np.sum(layer.data[:8, :8] == 3) == 41 assert np.sum(layer.data[9:22, 2:15] == 4) == 137 assert np.sum(layer.data[24:37, 2:15] == 5) == 137 assert np.sum(layer.data[33:, 33:] == 6) == 41 assert np.sum(layer.data[5:26, 17:38] == 7) == 349
def test_paint(Event, brush_shape, expected_sum): """Test painting labels with circle/square brush.""" data = np.ones((20, 20), dtype=np.int32) layer = Labels(data) layer.brush_size = 10 assert layer.cursor_size == 10 with pytest.warns(FutureWarning): layer.brush_shape = brush_shape layer.mode = 'paint' layer.selected_label = 3 # Simulate click event = ReadOnlyWrapper( Event(type='mouse_press', is_dragging=False, position=(0, 0))) mouse_press_callbacks(layer, event) # Simulate drag event = ReadOnlyWrapper( Event(type='mouse_move', is_dragging=True, position=(19, 19))) mouse_move_callbacks(layer, event) # Simulate release event = ReadOnlyWrapper( Event(type='mouse_release', is_dragging=False, position=(19, 19))) mouse_release_callbacks(layer, event) # Painting goes from (0, 0) to (19, 19) with a brush size of 10, changing # all pixels along that path, but none outside it. assert np.unique(layer.data[:8, :8]) == 3 assert np.unique(layer.data[-8:, -8:]) == 3 assert np.unique(layer.data[:5, -5:]) == 1 assert np.unique(layer.data[-5:, :5]) == 1 assert np.sum(layer.data == 3) == expected_sum
def test_erase(Event, brush_shape, expected_sum): """Test erasing labels with different brush shapes.""" data = np.ones((20, 20)) layer = Labels(data) layer.brush_size = 10 layer.mode = 'erase' layer.brush_shape = brush_shape layer.selected_label = 3 layer.position = (0, 0) # Simulate click event = ReadOnlyWrapper(Event(type='mouse_press', is_dragging=False)) mouse_press_callbacks(layer, event) layer.position = (19, 19) # Simulate drag event = ReadOnlyWrapper(Event(type='mouse_move', is_dragging=True)) mouse_move_callbacks(layer, event) # Simulate release event = ReadOnlyWrapper(Event(type='mouse_release', is_dragging=False)) mouse_release_callbacks(layer, event) # Painting goes from (0, 0) to (19, 19) with a brush size of 10, changing # all pixels along that path, but non outside it. assert np.unique(layer.data[:8, :8]) == 0 assert np.unique(layer.data[-8:, -8:]) == 0 assert np.unique(layer.data[:5, -5:]) == 1 assert np.unique(layer.data[-5:, :5]) == 1 assert np.sum(layer.data == 1) == expected_sum
def test_paint_scale(Event, brush_shape, expected_sum): """Test painting labels with circle/square brush when scaled.""" data = np.ones((20, 20)) layer = Labels(data, scale=(2, 2)) layer.brush_size = 20 layer.brush_shape = brush_shape layer.mode = 'paint' layer.selected_label = 3 layer.position = (0, 0) # Simulate click event = ReadOnlyWrapper(Event(type='mouse_press', is_dragging=False)) mouse_press_callbacks(layer, event) layer.position = (39, 39) # Simulate drag event = ReadOnlyWrapper(Event(type='mouse_move', is_dragging=True)) mouse_move_callbacks(layer, event) # Simulate release event = ReadOnlyWrapper(Event(type='mouse_release', is_dragging=False)) mouse_release_callbacks(layer, event) # Painting goes from (0, 0) to (19, 19) with a brush size of 10, changing # all pixels along that path, but none outside it. assert np.unique(layer.data[:8, :8]) == 3 assert np.unique(layer.data[-8:, -8:]) == 3 assert np.unique(layer.data[:5, -5:]) == 1 assert np.unique(layer.data[-5:, :5]) == 1 assert np.sum(layer.data == 3) == expected_sum
def test_undo_redo( brush_shape, brush_size, mode, selected_label, preserve_labels, n_dimensional, ): blobs = data.binary_blobs(length=64, volume_fraction=0.3, n_dim=3) layer = Labels(blobs) data_history = [blobs.copy()] with pytest.warns(FutureWarning): layer.brush_shape = brush_shape layer.brush_size = brush_size layer.mode = mode layer.selected_label = selected_label layer.preserve_labels = preserve_labels layer.n_edit_dimensions = 3 if n_dimensional else 2 coord = np.random.random((3, )) * (np.array(blobs.shape) - 1) while layer.data[tuple(coord.astype(int))] == 0 and np.any(layer.data): coord = np.random.random((3, )) * (np.array(blobs.shape) - 1) if layer.mode == 'fill': layer.fill(coord, layer.selected_label) if layer.mode == 'erase': layer.paint(coord, 0) if layer.mode == 'paint': layer.paint(coord, layer.selected_label) data_history.append(np.copy(layer.data)) layer.undo() np.testing.assert_array_equal(layer.data, data_history[0]) layer.redo() np.testing.assert_array_equal(layer.data, data_history[1])
def test_ndim_paint(): test_array = np.zeros((5, 6, 7, 8), dtype=int) layer = Labels(test_array) layer.n_edit_dimensions = 3 with pytest.warns(FutureWarning): layer.brush_shape = 'circle' layer.brush_size = 2 # equivalent to 18-connected 3D neighborhood layer.paint((1, 1, 1, 1), 1) assert np.sum(layer.data) == 19 # 18 + center assert not np.any(layer.data[0]) and not np.any(layer.data[2:]) layer.n_edit_dimensions = 2 # 3x3 square layer._dims_order = [1, 2, 0, 3] layer.paint((4, 5, 6, 7), 8) assert len(np.flatnonzero(layer.data == 8)) == 4 # 2D square is in corner np.testing.assert_array_equal( test_array[:, 5, 6, :], np.array([ [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 8, 8], [0, 0, 0, 0, 0, 0, 8, 8], ]), )
def test_brush_size(): """Test changing brush size.""" np.random.seed(0) data = np.random.randint(20, size=(10, 15)) layer = Labels(data) assert layer.brush_size == 10 layer.brush_size = 20 assert layer.brush_size == 20
def test_paint(): """Test painting labels with different brush sizes.""" np.random.seed(0) data = np.random.randint(20, size=(10, 15)) data[:10, :10] = 1 layer = Labels(data) assert np.unique(layer.data[:5, :5]) == 1 assert np.unique(layer.data[5:10, 5:10]) == 1 layer.brush_size = 10 layer.paint([0, 0], 2) assert np.unique(layer.data[:5, :5]) == 2 assert np.unique(layer.data[5:10, 5:10]) == 1 layer.brush_size = 20 layer.paint([0, 0], 2) assert np.unique(layer.data[:5, :5]) == 2 assert np.unique(layer.data[5:10, 5:10]) == 2
def test_paint(MouseEvent): """Test painting labels with circle brush.""" data = np.ones((20, 20), dtype=np.int32) layer = Labels(data) layer.brush_size = 10 assert layer.cursor_size == 10 layer.mode = 'paint' layer.selected_label = 3 # Simulate click event = ReadOnlyWrapper( MouseEvent( type='mouse_press', is_dragging=False, position=(0, 0), view_direction=None, dims_displayed=(0, 1), dims_point=(0, 0), ) ) mouse_press_callbacks(layer, event) # Simulate drag event = ReadOnlyWrapper( MouseEvent( type='mouse_move', is_dragging=True, position=(19, 19), view_direction=None, dims_displayed=(0, 1), dims_point=(0, 0), ) ) mouse_move_callbacks(layer, event) # Simulate release event = ReadOnlyWrapper( MouseEvent( type='mouse_release', is_dragging=False, position=(19, 19), view_direction=None, dims_displayed=(0, 1), dims_point=(0, 0), ) ) mouse_release_callbacks(layer, event) # Painting goes from (0, 0) to (19, 19) with a brush size of 10, changing # all pixels along that path, but none outside it. assert np.unique(layer.data[:8, :8]) == 3 assert np.unique(layer.data[-8:, -8:]) == 3 assert np.unique(layer.data[:5, -5:]) == 1 assert np.unique(layer.data[-5:, :5]) == 1 assert np.sum(layer.data == 3) == 244
def test_paint_2d_xarray(): """Test the memory usage of painting an xarray indirectly via timeout.""" data = xr.DataArray(np.zeros((3, 3, 1024, 1024), dtype=np.uint32)) layer = Labels(data) layer.brush_size = 12 layer.mode = 'paint' layer.paint((1, 1, 512, 512), 3) assert isinstance(layer.data, xr.DataArray) assert layer.data.sum() == 411
def test_negative_label(): """Test negative label values are supported.""" data = np.random.randint(low=-1, high=20, size=(10, 10)) original_data = np.copy(data) layer = Labels(data) layer.selected_label = -1 layer.brush_size = 3 layer.paint((5, 5), -1) assert np.count_nonzero(layer.data == -1) > np.count_nonzero( original_data == -1)
def test_paint_3d_negative_scale(scale): labels = np.zeros((3, 5, 11, 11), dtype=int) labels_layer = Labels(labels, scale=(1, ) + scale, translate=(-200, 100, 100)) labels_layer.n_edit_dimensions = 3 labels_layer.brush_size = 8 labels_layer.paint((1, 2, 5, 5), 1) np.testing.assert_array_equal(np.sum(labels_layer.data, axis=(1, 2, 3)), [0, 95, 0])
def test_paint_2d_xarray(brush_shape, expected_sum): """Test the memory usage of painting an xarray indirectly via timeout.""" data = xr.DataArray(np.zeros((3, 3, 1024, 1024), dtype=np.uint32)) layer = Labels(data) layer.brush_size = 12 with pytest.warns(FutureWarning): layer.brush_shape = brush_shape layer.mode = 'paint' layer.paint((1, 1, 512, 512), 3) assert isinstance(layer.data, xr.DataArray) assert layer.data.sum() == expected_sum
def test_paint_2d_xarray(): """Test the memory usage of painting an xarray indirectly via timeout.""" now = time.monotonic() data = xr.DataArray(np.zeros((3, 3, 1024, 1024), dtype=np.uint32)) layer = Labels(data) layer.brush_size = 12 layer.mode = 'paint' layer.paint((1, 1, 512, 512), 3) assert isinstance(layer.data, xr.DataArray) assert layer.data.sum() == 411 elapsed = time.monotonic() - now assert elapsed < 1, "test was too slow, computation was likely not lazy"
def test_paint_with_preserve_labels(): """Test painting labels while preserving existing labels""" data = np.zeros((15, 10)) data[:3, :3] = 1 layer = Labels(data) layer.preserve_labels = True assert np.unique(layer.data[:3, :3]) == 1 layer.brush_size = 9 layer.paint([0, 0], 2) assert np.unique(layer.data[3:5, 0:5]) == 2 assert np.unique(layer.data[0:5, 3:5]) == 2 assert np.unique(layer.data[:3, :3]) == 1
def test_paint_with_preserve_labels(): """Test painting labels with square brush while preserving existing labels.""" data = np.zeros((15, 10), dtype=np.uint32) data[:3, :3] = 1 layer = Labels(data) with pytest.warns(FutureWarning): layer.brush_shape = 'square' layer.preserve_labels = True assert np.unique(layer.data[:3, :3]) == 1 layer.brush_size = 9 layer.paint([0, 0], 2) assert np.unique(layer.data[3:5, 0:5]) == 2 assert np.unique(layer.data[0:5, 3:5]) == 2 assert np.unique(layer.data[:3, :3]) == 1
def test_mouse_move(): """Test painting labels with different brush sizes.""" np.random.seed(0) data = np.random.randint(20, size=(20, 20)) layer = Labels(data) layer.brush_size = 10 layer.mode = 'paint' layer.selected_label = 3 layer._last_cursor_coord = (0, 0) layer.coordinates = (19, 19) Event = collections.namedtuple('Event', 'is_dragging') event = Event(is_dragging=True) layer.on_mouse_move(event) assert np.unique(layer.data[:5, :5]) == 3 assert np.unique(layer.data[-5:, -5:]) == 3
def test_paint_3d(MouseEvent): """Test filling label nD.""" data = np.zeros((21, 21, 21), dtype=np.int32) data[10, 10, 10] = 1 layer = Labels(data) layer._slice_dims(point=(0, 0, 0), ndisplay=3) layer.n_edit_dimensions = 3 layer.mode = 'paint' layer.selected_label = 4 layer.brush_size = 3 # Simulate click event = ReadOnlyWrapper( MouseEvent( type='mouse_press', is_dragging=False, position=(0.1, 0, 0), view_direction=np.full(3, np.sqrt(3)), dims_displayed=(0, 1, 2), dims_point=(0, 0, 0), ) ) mouse_press_callbacks(layer, event) np.testing.assert_array_equal(np.unique(layer.data), [0, 4]) num_filled = np.bincount(layer.data.ravel())[4] assert num_filled > 1 layer.mode = 'erase' # Simulate click event = ReadOnlyWrapper( MouseEvent( type='mouse_press', is_dragging=False, position=(0, 10, 10), view_direction=(1, 0, 0), dims_displayed=(0, 1, 2), dims_point=(0, 0, 0), ) ) mouse_press_callbacks(layer, event) new_num_filled = np.bincount(layer.data.ravel())[4] assert new_num_filled < num_filled
def test_paint_3d(): """Test painting labels with circle brush on 3D image.""" data = np.zeros((30, 40, 40), dtype=np.uint32) layer = Labels(data) layer.brush_size = 12 layer.mode = 'paint' # Paint in 2D layer.paint((10, 10, 10), 3) # Paint in 3D layer.n_edit_dimensions = 3 layer.paint((10, 25, 10), 4) # Paint in 3D, preserve labels layer.n_edit_dimensions = 3 layer.preserve_labels = True layer.paint((10, 15, 15), 5) assert np.sum(layer.data[4:17, 4:17, 4:17] == 3) == 137 assert np.sum(layer.data[4:17, 19:32, 4:17] == 4) == 1189 assert np.sum(layer.data[4:17, 9:32, 9:32] == 5) == 1103
def test_erase_3d_undo_empty(MouseEvent): """Nothing should be added to undo queue when clicks fall outside data.""" data = np.zeros((20, 20, 20), dtype=np.int32) data[10, :, :] = 1 layer = Labels(data) layer.brush_size = 5 layer.mode = 'erase' layer._slice_dims(point=(0, 0, 0), ndisplay=3) layer.n_edit_dimensions = 3 # Simulate click, outside data event = ReadOnlyWrapper( MouseEvent( type='mouse_press', is_dragging=False, position=(-1, -1, -1), view_direction=(1, 0, 0), dims_displayed=(0, 1, 2), dims_point=(0, 0, 0), ) ) mouse_press_callbacks(layer, event) # Simulate release event = ReadOnlyWrapper( MouseEvent( type='mouse_release', is_dragging=False, position=(-1, -1, -1), view_direction=(1, 0, 0), dims_displayed=(0, 1, 2), dims_point=(0, 0, 0), ) ) mouse_release_callbacks(layer, event) # Undo queue should be empty assert len(layer._undo_history) == 0
def test_paint_3d(brush_shape, expected_sum): """Test painting labels with circle/square brush on 3D image.""" data = np.zeros((30, 40, 40)) layer = Labels(data) layer.brush_size = 12 layer.brush_shape = brush_shape layer.mode = 'paint' # Paint in 2D layer.paint((10, 10, 10), 3) # Paint in 3D layer.n_dimensional = True layer.paint((10, 25, 10), 4) # Paint in 3D, preserve labels layer.n_dimensional = True layer.preserve_labels = True layer.paint((10, 15, 15), 5) assert np.sum(layer.data[4:17, 4:17, 4:17] == 3) == expected_sum[0] assert np.sum(layer.data[4:17, 19:32, 4:17] == 4) == expected_sum[1] assert np.sum(layer.data[4:17, 9:32, 9:32] == 5) == expected_sum[2]
def test_erase_3d_undo(MouseEvent): """Test erasing labels in 3D then undoing the erase. Specifically, this test checks that undo is correctly filled even when a click and drag starts outside of the data volume. """ data = np.zeros((20, 20, 20), dtype=np.int32) data[10, :, :] = 1 layer = Labels(data) layer.brush_size = 5 layer.mode = 'erase' layer._slice_dims(point=(0, 0, 0), ndisplay=3) layer.n_edit_dimensions = 3 # Simulate click event = ReadOnlyWrapper( MouseEvent( type='mouse_press', is_dragging=False, position=(-1, -1, -1), view_direction=(1, 0, 0), dims_displayed=(0, 1, 2), dims_point=(0, 0, 0), ) ) mouse_press_callbacks(layer, event) # Simulate drag. Note: we need to include top left and bottom right in the # drag or there are no coordinates to interpolate event = ReadOnlyWrapper( MouseEvent( type='mouse_move', is_dragging=True, position=(-1, 0.1, 0.1), view_direction=(1, 0, 0), dims_displayed=(0, 1, 2), dims_point=(0, 0, 0), ) ) mouse_move_callbacks(layer, event) event = ReadOnlyWrapper( MouseEvent( type='mouse_move', is_dragging=True, position=(-1, 18.9, 18.9), view_direction=(1, 0, 0), dims_displayed=(0, 1, 2), dims_point=(0, 0, 0), ) ) mouse_move_callbacks(layer, event) # Simulate release event = ReadOnlyWrapper( MouseEvent( type='mouse_release', is_dragging=False, position=(-1, 21, 21), view_direction=(1, 0, 0), dims_displayed=(0, 1, 2), dims_point=(0, 0, 0), ) ) mouse_release_callbacks(layer, event) # Erasing goes from (-1, -1, -1) to (-1, 21, 21), should split the labels # into two sections. Undoing should work and reunite the labels to one # square assert ndi.label(layer.data)[1] == 2 layer.undo() assert ndi.label(layer.data)[1] == 1