1import numpy as np 2import pytest 3from numpy.testing import assert_almost_equal 4 5from skimage import color, data, draw, feature, img_as_float 6from skimage._shared import filters 7from skimage._shared._warnings import expected_warnings 8from skimage._shared.testing import fetch 9from skimage._shared.utils import _supported_float_type 10 11 12def test_hog_output_size(): 13 img = img_as_float(data.astronaut()[:256, :].mean(axis=2)) 14 15 fd = feature.hog(img, orientations=9, pixels_per_cell=(8, 8), 16 cells_per_block=(1, 1), block_norm='L1') 17 18 assert len(fd) == 9 * (256 // 8) * (512 // 8) 19 20 21@pytest.mark.parametrize('dtype', [np.float32, np.float64]) 22def test_hog_output_correctness_l1_norm(dtype): 23 img = color.rgb2gray(data.astronaut()).astype(dtype=dtype, copy=False) 24 correct_output = np.load(fetch('data/astronaut_GRAY_hog_L1.npy')) 25 26 output = feature.hog(img, orientations=9, pixels_per_cell=(8, 8), 27 cells_per_block=(3, 3), block_norm='L1', 28 feature_vector=True, transform_sqrt=False, 29 visualize=False) 30 float_dtype = _supported_float_type(dtype) 31 assert output.dtype == float_dtype 32 decimal = 7 if float_dtype == np.float64 else 5 33 assert_almost_equal(output, correct_output, decimal=decimal) 34 35 36@pytest.mark.parametrize('dtype', [np.float32, np.float64]) 37def test_hog_output_correctness_l2hys_norm(dtype): 38 img = color.rgb2gray(data.astronaut()).astype(dtype=dtype, copy=False) 39 correct_output = np.load(fetch('data/astronaut_GRAY_hog_L2-Hys.npy')) 40 41 output = feature.hog(img, orientations=9, pixels_per_cell=(8, 8), 42 cells_per_block=(3, 3), block_norm='L2-Hys', 43 feature_vector=True, transform_sqrt=False, 44 visualize=False) 45 float_dtype = _supported_float_type(dtype) 46 assert output.dtype == float_dtype 47 decimal = 7 if float_dtype == np.float64 else 5 48 assert_almost_equal(output, correct_output, decimal=decimal) 49 50 51def test_hog_image_size_cell_size_mismatch(): 52 image = data.camera()[:150, :200] 53 fd = feature.hog(image, orientations=9, pixels_per_cell=(8, 8), 54 cells_per_block=(1, 1), block_norm='L1') 55 assert len(fd) == 9 * (150 // 8) * (200 // 8) 56 57 58def test_hog_odd_cell_size(): 59 img = np.zeros((3, 3)) 60 img[2, 2] = 1 61 62 correct_output = np.zeros((9, )) 63 correct_output[0] = 0.5 64 correct_output[4] = 0.5 65 66 output = feature.hog(img, pixels_per_cell=(3, 3), 67 cells_per_block=(1, 1), block_norm='L1') 68 69 assert_almost_equal(output, correct_output, decimal=1) 70 71 72def test_hog_basic_orientations_and_data_types(): 73 # scenario: 74 # 1) create image (with float values) where upper half is filled by 75 # zeros, bottom half by 100 76 # 2) create unsigned integer version of this image 77 # 3) calculate feature.hog() for both images, both with 'transform_sqrt' 78 # option enabled and disabled 79 # 4) verify that all results are equal where expected 80 # 5) verify that computed feature vector is as expected 81 # 6) repeat the scenario for 90, 180 and 270 degrees rotated images 82 83 # size of testing image 84 width = height = 35 85 86 image0 = np.zeros((height, width), dtype='float') 87 image0[height // 2:] = 100 88 89 for rot in range(4): 90 # rotate by 0, 90, 180 and 270 degrees 91 image_float = np.rot90(image0, rot) 92 93 # create uint8 image from image_float 94 image_uint8 = image_float.astype('uint8') 95 96 (hog_float, hog_img_float) = feature.hog( 97 image_float, orientations=4, pixels_per_cell=(8, 8), 98 cells_per_block=(1, 1), visualize=True, transform_sqrt=False, 99 block_norm='L1') 100 (hog_uint8, hog_img_uint8) = feature.hog( 101 image_uint8, orientations=4, pixels_per_cell=(8, 8), 102 cells_per_block=(1, 1), visualize=True, transform_sqrt=False, 103 block_norm='L1') 104 (hog_float_norm, hog_img_float_norm) = feature.hog( 105 image_float, orientations=4, pixels_per_cell=(8, 8), 106 cells_per_block=(1, 1), visualize=True, transform_sqrt=True, 107 block_norm='L1') 108 (hog_uint8_norm, hog_img_uint8_norm) = feature.hog( 109 image_uint8, orientations=4, pixels_per_cell=(8, 8), 110 cells_per_block=(1, 1), visualize=True, transform_sqrt=True, 111 block_norm='L1') 112 113 # set to True to enable manual debugging with graphical output, 114 # must be False for automatic testing 115 if False: 116 import matplotlib.pyplot as plt 117 plt.figure() 118 plt.subplot(2, 3, 1) 119 plt.imshow(image_float) 120 plt.colorbar() 121 plt.title('image') 122 plt.subplot(2, 3, 2) 123 plt.imshow(hog_img_float) 124 plt.colorbar() 125 plt.title('HOG result visualisation (float img)') 126 plt.subplot(2, 3, 5) 127 plt.imshow(hog_img_uint8) 128 plt.colorbar() 129 plt.title('HOG result visualisation (uint8 img)') 130 plt.subplot(2, 3, 3) 131 plt.imshow(hog_img_float_norm) 132 plt.colorbar() 133 plt.title('HOG result (transform_sqrt) visualisation (float img)') 134 plt.subplot(2, 3, 6) 135 plt.imshow(hog_img_uint8_norm) 136 plt.colorbar() 137 plt.title('HOG result (transform_sqrt) visualisation (uint8 img)') 138 plt.show() 139 140 # results (features and visualisation) for float and uint8 images must 141 # be almost equal 142 assert_almost_equal(hog_float, hog_uint8) 143 assert_almost_equal(hog_img_float, hog_img_uint8) 144 145 # resulting features should be almost equal 146 # when 'transform_sqrt' is enabled 147 # or disabled (for current simple testing image) 148 assert_almost_equal(hog_float, hog_float_norm, decimal=4) 149 assert_almost_equal(hog_float, hog_uint8_norm, decimal=4) 150 151 # reshape resulting feature vector to matrix with 4 columns (each 152 # corresponding to one of 4 directions); only one direction should 153 # contain nonzero values (this is manually determined for testing 154 # image) 155 actual = np.max(hog_float.reshape(-1, 4), axis=0) 156 157 if rot in [0, 2]: 158 # image is rotated by 0 and 180 degrees 159 desired = [0, 0, 1, 0] 160 elif rot in [1, 3]: 161 # image is rotated by 90 and 270 degrees 162 desired = [1, 0, 0, 0] 163 else: 164 raise Exception('Result is not determined for this rotation.') 165 166 assert_almost_equal(actual, desired, decimal=2) 167 168 169def test_hog_orientations_circle(): 170 # scenario: 171 # 1) create image with blurred circle in the middle 172 # 2) calculate feature.hog() 173 # 3) verify that the resulting feature vector contains uniformly 174 # distributed values for all orientations, i.e. no orientation is 175 # lost or emphasized 176 # 4) repeat the scenario for other 'orientations' option 177 178 # size of testing image 179 width = height = 100 180 181 image = np.zeros((height, width)) 182 rr, cc = draw.disk((int(height / 2), int(width / 2)), int(width / 3)) 183 image[rr, cc] = 100 184 image = filters.gaussian(image, 2, mode='reflect') 185 186 for orientations in range(2, 15): 187 (hog, hog_img) = feature.hog(image, orientations=orientations, 188 pixels_per_cell=(8, 8), 189 cells_per_block=(1, 1), visualize=True, 190 transform_sqrt=False, 191 block_norm='L1') 192 193 # set to True to enable manual debugging with graphical output, 194 # must be False for automatic testing 195 if False: 196 import matplotlib.pyplot as plt 197 plt.figure() 198 plt.subplot(1, 2, 1) 199 plt.imshow(image) 200 plt.colorbar() 201 plt.title('image_float') 202 plt.subplot(1, 2, 2) 203 plt.imshow(hog_img) 204 plt.colorbar() 205 plt.title('HOG result visualisation, ' 206 'orientations=%d' % (orientations)) 207 plt.show() 208 209 # reshape resulting feature vector to matrix with N columns (each 210 # column corresponds to one direction), 211 hog_matrix = hog.reshape(-1, orientations) 212 213 # compute mean values in the resulting feature vector for each 214 # direction, these values should be almost equal to the global mean 215 # value (since the image contains a circle), i.e., all directions have 216 # same contribution to the result 217 actual = np.mean(hog_matrix, axis=0) 218 desired = np.mean(hog_matrix) 219 assert_almost_equal(actual, desired, decimal=1) 220 221 222def test_hog_visualization_orientation(): 223 """Test that the visualization produces a line with correct orientation 224 225 The hog visualization is expected to draw line segments perpendicular to 226 the midpoints of orientation bins. This example verifies that when 227 orientations=3 and the gradient is entirely in the middle bin (bisected 228 by the y-axis), the line segment drawn by the visualization is horizontal. 229 """ 230 231 width = height = 11 232 233 image = np.zeros((height, width), dtype='float') 234 image[height // 2:] = 1 235 236 _, hog_image = feature.hog( 237 image, 238 orientations=3, 239 pixels_per_cell=(width, height), 240 cells_per_block=(1, 1), 241 visualize=True, 242 block_norm='L1' 243 ) 244 245 middle_index = height // 2 246 indices_excluding_middle = [x for x in range(height) if x != middle_index] 247 248 assert (hog_image[indices_excluding_middle, :] == 0).all() 249 assert (hog_image[middle_index, 1:-1] > 0).all() 250 251 252def test_hog_block_normalization_incorrect_error(): 253 img = np.eye(4) 254 with pytest.raises(ValueError): 255 feature.hog(img, block_norm='Linf') 256 257 258@pytest.mark.parametrize("shape,multichannel", [ 259 ((3, 3, 3), False), 260 ((3, 3), True), 261 ((3, 3, 3, 3), True), 262]) 263def test_hog_incorrect_dimensions(shape, multichannel): 264 img = np.zeros(shape) 265 with pytest.raises(ValueError): 266 with expected_warnings(["`multichannel` is a deprecated argument"]): 267 feature.hog(img, multichannel=multichannel, block_norm='L1') 268 269 270def test_hog_output_equivariance_deprecated_multichannel(): 271 img = data.astronaut() 272 img[:, :, (1, 2)] = 0 273 with expected_warnings(["`multichannel` is a deprecated argument"]): 274 hog_ref = feature.hog(img, multichannel=True, block_norm='L1') 275 276 for n in (1, 2): 277 with expected_warnings(["`multichannel` is a deprecated argument"]): 278 hog_fact = feature.hog(np.roll(img, n, axis=2), multichannel=True, 279 block_norm='L1') 280 assert_almost_equal(hog_ref, hog_fact) 281 282 # repeat prior test, but check for positional multichannel warning 283 with expected_warnings(["Providing the `multichannel` argument"]): 284 hog_fact = feature.hog(np.roll(img, n, axis=2), 9, (8, 8), (3, 3), 285 'L1', False, False, True, True) 286 assert_almost_equal(hog_ref, hog_fact) 287 288 289@pytest.mark.parametrize('channel_axis', [0, 1, -1, -2]) 290def test_hog_output_equivariance_channel_axis(channel_axis): 291 img = data.astronaut()[:64, :32] 292 img[:, :, (1, 2)] = 0 293 img = np.moveaxis(img, -1, channel_axis) 294 hog_ref = feature.hog(img, channel_axis=channel_axis, block_norm='L1') 295 296 for n in (1, 2): 297 hog_fact = feature.hog(np.roll(img, n, axis=channel_axis), 298 channel_axis=channel_axis, block_norm='L1') 299 assert_almost_equal(hog_ref, hog_fact) 300