1import numpy as np
2import pytest
3
4from skimage._shared.testing import expected_warnings, test_parallel
5from skimage.feature import (graycomatrix,
6                             graycoprops,
7                             greycomatrix,
8                             greycoprops,
9                             local_binary_pattern,
10                             multiblock_lbp)
11from skimage.transform import integral_image
12
13
14class TestGLCM():
15
16    def setup(self):
17        self.image = np.array([[0, 0, 1, 1],
18                               [0, 0, 1, 1],
19                               [0, 2, 2, 2],
20                               [2, 2, 3, 3]], dtype=np.uint8)
21
22    @test_parallel()
23    def test_output_angles(self):
24        result = graycomatrix(
25            self.image, [1], [0, np.pi / 4, np.pi / 2, 3 * np.pi / 4], 4
26        )
27        assert result.shape == (4, 4, 1, 4)
28        expected1 = np.array([[2, 2, 1, 0],
29                             [0, 2, 0, 0],
30                             [0, 0, 3, 1],
31                             [0, 0, 0, 1]], dtype=np.uint32)
32        np.testing.assert_array_equal(result[:, :, 0, 0], expected1)
33        expected2 = np.array([[1, 1, 3, 0],
34                             [0, 1, 1, 0],
35                             [0, 0, 0, 2],
36                             [0, 0, 0, 0]], dtype=np.uint32)
37        np.testing.assert_array_equal(result[:, :, 0, 1], expected2)
38        expected3 = np.array([[3, 0, 2, 0],
39                             [0, 2, 2, 0],
40                             [0, 0, 1, 2],
41                             [0, 0, 0, 0]], dtype=np.uint32)
42        np.testing.assert_array_equal(result[:, :, 0, 2], expected3)
43        expected4 = np.array([[2, 0, 0, 0],
44                             [1, 1, 2, 0],
45                             [0, 0, 2, 1],
46                             [0, 0, 0, 0]], dtype=np.uint32)
47        np.testing.assert_array_equal(result[:, :, 0, 3], expected4)
48
49    def test_output_symmetric_1(self):
50        result = graycomatrix(self.image, [1], [np.pi / 2], 4,
51                              symmetric=True)
52        assert result.shape == (4, 4, 1, 1)
53        expected = np.array([[6, 0, 2, 0],
54                             [0, 4, 2, 0],
55                             [2, 2, 2, 2],
56                             [0, 0, 2, 0]], dtype=np.uint32)
57        np.testing.assert_array_equal(result[:, :, 0, 0], expected)
58
59    def test_error_raise_float(self):
60        for dtype in [
61                float, np.double, np.float16, np.float32, np.float64
62                ]:
63            with pytest.raises(ValueError):
64                graycomatrix(self.image.astype(dtype), [1], [np.pi], 4)
65
66    def test_error_raise_int_types(self):
67        for dtype in [
68                np.int16, np.int32, np.int64, np.uint16, np.uint32, np.uint64
69                ]:
70            with pytest.raises(ValueError):
71                graycomatrix(self.image.astype(dtype), [1], [np.pi])
72
73    def test_error_raise_negative(self):
74        with pytest.raises(ValueError):
75            graycomatrix(self.image.astype(np.int16) - 1, [1], [np.pi], 4)
76
77    def test_error_raise_levels_smaller_max(self):
78        with pytest.raises(ValueError):
79            graycomatrix(self.image - 1, [1], [np.pi], 3)
80
81    def test_image_data_types(self):
82        for dtype in [
83            np.uint16, np.uint32, np.uint64, np.int16, np.int32, np.int64
84        ]:
85            img = self.image.astype(dtype)
86            result = graycomatrix(img, [1], [np.pi / 2], 4,
87                                  symmetric=True)
88            assert result.shape == (4, 4, 1, 1)
89            expected = np.array([[6, 0, 2, 0],
90                                 [0, 4, 2, 0],
91                                 [2, 2, 2, 2],
92                                 [0, 0, 2, 0]], dtype=np.uint32)
93            np.testing.assert_array_equal(result[:, :, 0, 0], expected)
94
95        return
96
97    def test_output_distance(self):
98        im = np.array([[0, 0, 0, 0],
99                       [1, 0, 0, 1],
100                       [2, 0, 0, 2],
101                       [3, 0, 0, 3]], dtype=np.uint8)
102        result = graycomatrix(im, [3], [0], 4, symmetric=False)
103        expected = np.array([[1, 0, 0, 0],
104                             [0, 1, 0, 0],
105                             [0, 0, 1, 0],
106                             [0, 0, 0, 1]], dtype=np.uint32)
107        np.testing.assert_array_equal(result[:, :, 0, 0], expected)
108
109    def test_output_combo(self):
110        im = np.array([[0],
111                       [1],
112                       [2],
113                       [3]], dtype=np.uint8)
114        result = graycomatrix(im, [1, 2], [0, np.pi / 2], 4)
115        assert result.shape == (4, 4, 2, 2)
116
117        z = np.zeros((4, 4), dtype=np.uint32)
118        e1 = np.array([[0, 1, 0, 0],
119                       [0, 0, 1, 0],
120                       [0, 0, 0, 1],
121                       [0, 0, 0, 0]], dtype=np.uint32)
122        e2 = np.array([[0, 0, 1, 0],
123                       [0, 0, 0, 1],
124                       [0, 0, 0, 0],
125                       [0, 0, 0, 0]], dtype=np.uint32)
126
127        np.testing.assert_array_equal(result[:, :, 0, 0], z)
128        np.testing.assert_array_equal(result[:, :, 1, 0], z)
129        np.testing.assert_array_equal(result[:, :, 0, 1], e1)
130        np.testing.assert_array_equal(result[:, :, 1, 1], e2)
131
132    def test_output_empty(self):
133        result = graycomatrix(self.image, [10], [0], 4)
134        np.testing.assert_array_equal(result[:, :, 0, 0],
135                                      np.zeros((4, 4), dtype=np.uint32))
136        result = graycomatrix(self.image, [10], [0], 4, normed=True)
137        np.testing.assert_array_equal(result[:, :, 0, 0],
138                                      np.zeros((4, 4), dtype=np.uint32))
139
140    def test_normed_symmetric(self):
141        result = graycomatrix(self.image, [1, 2, 3],
142                              [0, np.pi / 2, np.pi], 4,
143                              normed=True, symmetric=True)
144        for d in range(result.shape[2]):
145            for a in range(result.shape[3]):
146                np.testing.assert_almost_equal(result[:, :, d, a].sum(),
147                                               1.0)
148                np.testing.assert_array_equal(result[:, :, d, a],
149                                              result[:, :, d, a].transpose())
150
151    def test_contrast(self):
152        result = graycomatrix(self.image, [1, 2], [0], 4,
153                              normed=True, symmetric=True)
154        result = np.round(result, 3)
155        contrast = graycoprops(result, 'contrast')
156        np.testing.assert_almost_equal(contrast[0, 0], 0.585, decimal=3)
157
158    def test_dissimilarity(self):
159        result = graycomatrix(self.image, [1], [0, np.pi / 2], 4,
160                              normed=True, symmetric=True)
161        result = np.round(result, 3)
162        dissimilarity = graycoprops(result, 'dissimilarity')
163        np.testing.assert_almost_equal(dissimilarity[0, 0], 0.418, decimal=3)
164
165    def test_greycomatrix_and_greycoprops_deprecations(self):
166        expected = graycomatrix(self.image, [1], [0, np.pi / 2], 4,
167                                normed=True, symmetric=True)
168        with expected_warnings(["Function ``greycomatrix``"]):
169            result = greycomatrix(self.image, [1], [0, np.pi / 2], 4,
170                                  normed=True, symmetric=True)
171        np.testing.assert_array_equal(expected, result)
172
173        result = np.round(result, 3)
174        dissimilarity_expected = graycoprops(result, 'dissimilarity')
175        with expected_warnings(["Function ``greycoprops``"]):
176            dissimilarity_result = greycoprops(result, 'dissimilarity')
177        np.testing.assert_array_equal(
178            dissimilarity_expected, dissimilarity_result
179        )
180
181    def test_dissimilarity_2(self):
182        result = graycomatrix(self.image, [1, 3], [np.pi / 2], 4,
183                              normed=True, symmetric=True)
184        result = np.round(result, 3)
185        dissimilarity = graycoprops(result, 'dissimilarity')[0, 0]
186        np.testing.assert_almost_equal(dissimilarity, 0.665, decimal=3)
187
188    def test_non_normalized_glcm(self):
189        img = (np.random.random((100, 100)) * 8).astype(np.uint8)
190        p = graycomatrix(img, [1, 2, 4, 5], [0, 0.25, 1, 1.5], levels=8)
191        np.testing.assert_(np.max(graycoprops(p, 'correlation')) < 1.0)
192
193    def test_invalid_property(self):
194        result = graycomatrix(self.image, [1], [0], 4)
195        with pytest.raises(ValueError):
196            graycoprops(result, 'ABC')
197
198    def test_homogeneity(self):
199        result = graycomatrix(self.image, [1], [0, 6], 4, normed=True,
200                              symmetric=True)
201        homogeneity = graycoprops(result, 'homogeneity')[0, 0]
202        np.testing.assert_almost_equal(homogeneity, 0.80833333)
203
204    def test_energy(self):
205        result = graycomatrix(self.image, [1], [0, 4], 4, normed=True,
206                              symmetric=True)
207        energy = graycoprops(result, 'energy')[0, 0]
208        np.testing.assert_almost_equal(energy, 0.38188131)
209
210    def test_correlation(self):
211        result = graycomatrix(self.image, [1, 2], [0], 4, normed=True,
212                              symmetric=True)
213        energy = graycoprops(result, 'correlation')
214        np.testing.assert_almost_equal(energy[0, 0], 0.71953255)
215        np.testing.assert_almost_equal(energy[1, 0], 0.41176470)
216
217    def test_uniform_properties(self):
218        im = np.ones((4, 4), dtype=np.uint8)
219        result = graycomatrix(im, [1, 2, 8], [0, np.pi / 2], 4, normed=True,
220                              symmetric=True)
221        for prop in ['contrast', 'dissimilarity', 'homogeneity',
222                     'energy', 'correlation', 'ASM']:
223            graycoprops(result, prop)
224
225
226class TestLBP():
227
228    def setup(self):
229        self.image = np.array([[255,   6, 255,   0,  141,   0],
230                               [ 48, 250, 204, 166,  223,  63],
231                               [  8,   0, 159,  50,  255,  30],
232                               [167, 255,  63,  40,  128, 255],
233                               [  0, 255,  30,  34,  255,  24],
234                               [146, 241, 255,   0,  189, 126]],
235                              dtype='double')
236
237    @test_parallel()
238    def test_default(self):
239        lbp = local_binary_pattern(self.image, 8, 1, 'default')
240        ref = np.array([[  0, 251,   0, 255,  96, 255],
241                        [143,   0,  20, 153,  64,  56],
242                        [238, 255,  12, 191,   0, 252],
243                        [129,  64.,  62, 159, 199,   0],
244                        [255,   4, 255, 175,   0, 254],
245                        [  3,   5,   0, 255,   4,  24]])
246        np.testing.assert_array_equal(lbp, ref)
247
248    def test_ror(self):
249        lbp = local_binary_pattern(self.image, 8, 1, 'ror')
250        ref = np.array([[  0, 127,   0, 255,   3, 255],
251                        [ 31,   0,   5,  51,   1,   7],
252                        [119, 255,   3, 127,   0,  63],
253                        [  3,   1,  31,  63,  31,   0],
254                        [255,   1, 255,  95,   0, 127],
255                        [  3,   5,   0, 255,   1,   3]])
256        np.testing.assert_array_equal(lbp, ref)
257
258    def test_uniform(self):
259        lbp = local_binary_pattern(self.image, 8, 1, 'uniform')
260        ref = np.array([[0, 7, 0, 8, 2, 8],
261                        [5, 0, 9, 9, 1, 3],
262                        [9, 8, 2, 7, 0, 6],
263                        [2, 1, 5, 6, 5, 0],
264                        [8, 1, 8, 9, 0, 7],
265                        [2, 9, 0, 8, 1, 2]])
266        np.testing.assert_array_equal(lbp, ref)
267
268    def test_var(self):
269        # Test idea: mean of variance is estimate of overall variance.
270
271        # Fix random seed for test stability.
272        np.random.seed(13141516)
273
274        # Create random image with known variance.
275        image = np.random.rand(500, 500)
276        target_std = 0.3
277        image = image / image.std() * target_std
278
279        # Use P=4 to avoid interpolation effects
280        P, R = 4, 1
281        lbp = local_binary_pattern(image, P, R, 'var')
282
283        # Take central part to avoid border effect.
284        lbp = lbp[5:-5, 5:-5]
285
286        # The LBP variance is biased (ddof=0), correct for that.
287        expected = target_std**2 * (P-1)/P
288
289        np.testing.assert_almost_equal(lbp.mean(), expected, 4)
290
291    def test_nri_uniform(self):
292        lbp = local_binary_pattern(self.image, 8, 1, 'nri_uniform')
293        ref = np.array([[ 0, 54,  0, 57, 12, 57],
294                        [34,  0, 58, 58,  3, 22],
295                        [58, 57, 15, 50,  0, 47],
296                        [10,  3, 40, 42, 35,  0],
297                        [57,  7, 57, 58,  0, 56],
298                        [ 9, 58,  0, 57,  7, 14]])
299        np.testing.assert_array_almost_equal(lbp, ref)
300
301
302class TestMBLBP():
303
304    def test_single_mblbp(self):
305
306        # Create dummy matrix where first and fifth rectangles have greater
307        # value than the central one. Therefore, the following bits
308        # should be 1.
309        test_img = np.zeros((9, 9), dtype='uint8')
310        test_img[3:6, 3:6] = 1
311        test_img[:3, :3] = 255
312        test_img[6:, 6:] = 255
313
314        # MB-LBP is filled in reverse order. So the first and fifth bits from
315        # the end should be filled.
316        correct_answer = 0b10001000
317
318        int_img = integral_image(test_img)
319
320        lbp_code = multiblock_lbp(int_img, 0, 0, 3, 3)
321
322        np.testing.assert_equal(lbp_code, correct_answer)
323