1import numpy as np
2import pytest
3from skimage.morphology import remove_small_objects, remove_small_holes
4
5from skimage._shared import testing
6from skimage._shared.testing import assert_array_equal, assert_equal
7from skimage._shared._warnings import expected_warnings
8
9
10test_image = np.array([[0, 0, 0, 1, 0],
11                       [1, 1, 1, 0, 0],
12                       [1, 1, 1, 0, 1]], bool)
13
14
15def test_one_connectivity():
16    expected = np.array([[0, 0, 0, 0, 0],
17                         [1, 1, 1, 0, 0],
18                         [1, 1, 1, 0, 0]], bool)
19    observed = remove_small_objects(test_image, min_size=6)
20    assert_array_equal(observed, expected)
21
22
23def test_two_connectivity():
24    expected = np.array([[0, 0, 0, 1, 0],
25                         [1, 1, 1, 0, 0],
26                         [1, 1, 1, 0, 0]], bool)
27    observed = remove_small_objects(test_image, min_size=7, connectivity=2)
28    assert_array_equal(observed, expected)
29
30
31def test_in_place():
32    image = test_image.copy()
33    with expected_warnings(["in_place argument is deprecated"]):
34        observed = remove_small_objects(image, min_size=6, in_place=True)
35    assert_equal(observed is image, True,
36                 "remove_small_objects in_place argument failed.")
37
38
39@pytest.mark.parametrize("in_dtype", [bool, int, np.int32])
40@pytest.mark.parametrize("out_dtype", [bool, int, np.int32])
41def test_out(in_dtype, out_dtype):
42    image = test_image.astype(in_dtype, copy=True)
43    expected_out = np.empty_like(test_image, dtype=out_dtype)
44
45    if out_dtype != bool:
46        # object with only 1 label will warn on non-bool output dtype
47        exp_warn = ["Only one label was provided"]
48    else:
49        exp_warn = []
50
51    with expected_warnings(exp_warn):
52        out = remove_small_objects(image, min_size=6, out=expected_out)
53
54    assert out is expected_out
55
56
57def test_labeled_image():
58    labeled_image = np.array([[2, 2, 2, 0, 1],
59                              [2, 2, 2, 0, 1],
60                              [2, 0, 0, 0, 0],
61                              [0, 0, 3, 3, 3]], dtype=int)
62    expected = np.array([[2, 2, 2, 0, 0],
63                         [2, 2, 2, 0, 0],
64                         [2, 0, 0, 0, 0],
65                         [0, 0, 3, 3, 3]], dtype=int)
66    observed = remove_small_objects(labeled_image, min_size=3)
67    assert_array_equal(observed, expected)
68
69
70def test_uint_image():
71    labeled_image = np.array([[2, 2, 2, 0, 1],
72                              [2, 2, 2, 0, 1],
73                              [2, 0, 0, 0, 0],
74                              [0, 0, 3, 3, 3]], dtype=np.uint8)
75    expected = np.array([[2, 2, 2, 0, 0],
76                         [2, 2, 2, 0, 0],
77                         [2, 0, 0, 0, 0],
78                         [0, 0, 3, 3, 3]], dtype=np.uint8)
79    observed = remove_small_objects(labeled_image, min_size=3)
80    assert_array_equal(observed, expected)
81
82
83def test_single_label_warning():
84    image = np.array([[0, 0, 0, 1, 0],
85                      [1, 1, 1, 0, 0],
86                      [1, 1, 1, 0, 0]], int)
87    with expected_warnings(['use a boolean array?']):
88        remove_small_objects(image, min_size=6)
89
90
91def test_float_input():
92    float_test = np.random.rand(5, 5)
93    with testing.raises(TypeError):
94        remove_small_objects(float_test)
95
96
97def test_negative_input():
98    negative_int = np.random.randint(-4, -1, size=(5, 5))
99    with testing.raises(ValueError):
100        remove_small_objects(negative_int)
101
102
103test_holes_image = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
104                             [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
105                             [0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
106                             [0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
107                             [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
108                             [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
109                             [0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
110                             [0, 0, 0, 0, 0, 0, 0, 1, 1, 1]], bool)
111
112
113def test_one_connectivity_holes():
114    expected = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
115                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
116                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
117                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
118                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
119                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
120                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
121                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1]], bool)
122    observed = remove_small_holes(test_holes_image, area_threshold=3)
123    assert_array_equal(observed, expected)
124
125
126def test_two_connectivity_holes():
127    expected = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
128                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
129                         [0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
130                         [0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
131                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
132                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
133                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
134                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1]], bool)
135    observed = remove_small_holes(test_holes_image, area_threshold=3,
136                                  connectivity=2)
137    assert_array_equal(observed, expected)
138
139
140def test_in_place_holes():
141    image = test_holes_image.copy()
142    with expected_warnings(["in_place argument is deprecated"]):
143        observed = remove_small_holes(image, area_threshold=3, in_place=True)
144    assert_equal(observed is image, True,
145                 "remove_small_holes in_place argument failed.")
146
147
148def test_out_remove_small_holes():
149    image = test_holes_image.copy()
150    expected_out = np.empty_like(image)
151    out = remove_small_holes(image, area_threshold=3, out=expected_out)
152
153    assert out is expected_out
154
155
156def test_non_bool_out():
157    image = test_holes_image.copy()
158    expected_out = np.empty_like(image, dtype=int)
159    with testing.raises(TypeError):
160        remove_small_holes(image, area_threshold=3, out=expected_out)
161
162
163def test_labeled_image_holes():
164    labeled_holes_image = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
165                                    [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
166                                    [0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
167                                    [0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
168                                    [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
169                                    [0, 0, 0, 0, 0, 0, 0, 2, 2, 2],
170                                    [0, 0, 0, 0, 0, 0, 0, 2, 0, 2],
171                                    [0, 0, 0, 0, 0, 0, 0, 2, 2, 2]],
172                                   dtype=int)
173    expected = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
174                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
175                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
176                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
177                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
178                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
179                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
180                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1]], dtype=bool)
181    with expected_warnings(['returned as a boolean array']):
182        observed = remove_small_holes(labeled_holes_image, area_threshold=3)
183    assert_array_equal(observed, expected)
184
185
186def test_uint_image_holes():
187    labeled_holes_image = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
188                                    [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
189                                    [0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
190                                    [0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
191                                    [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
192                                    [0, 0, 0, 0, 0, 0, 0, 2, 2, 2],
193                                    [0, 0, 0, 0, 0, 0, 0, 2, 0, 2],
194                                    [0, 0, 0, 0, 0, 0, 0, 2, 2, 2]],
195                                   dtype=np.uint8)
196    expected = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
197                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
198                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
199                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
200                         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
201                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
202                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
203                         [0, 0, 0, 0, 0, 0, 0, 1, 1, 1]], dtype=bool)
204    with expected_warnings(['returned as a boolean array']):
205        observed = remove_small_holes(labeled_holes_image, area_threshold=3)
206    assert_array_equal(observed, expected)
207
208
209def test_label_warning_holes():
210    labeled_holes_image = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
211                                    [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
212                                    [0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
213                                    [0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
214                                    [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
215                                    [0, 0, 0, 0, 0, 0, 0, 2, 2, 2],
216                                    [0, 0, 0, 0, 0, 0, 0, 2, 0, 2],
217                                    [0, 0, 0, 0, 0, 0, 0, 2, 2, 2]],
218                                   dtype=int)
219    with expected_warnings(['use a boolean array?']):
220        remove_small_holes(labeled_holes_image, area_threshold=3)
221    remove_small_holes(labeled_holes_image.astype(bool), area_threshold=3)
222
223
224def test_float_input_holes():
225    float_test = np.random.rand(5, 5)
226    with testing.raises(TypeError):
227        remove_small_holes(float_test)
228