1"""Tests for color conversion functions. 2 3Authors 4------- 5- the rgb2hsv test was written by Nicolas Pinto, 2009 6- other tests written by Ralf Gommers, 2009 7 8:license: modified BSD 9""" 10 11import colorsys 12import numpy as np 13import pytest 14from numpy.testing import (assert_almost_equal, assert_array_almost_equal, 15 assert_equal) 16 17from skimage import data 18from skimage._shared._warnings import expected_warnings 19from skimage._shared.testing import fetch 20from skimage._shared.utils import _supported_float_type, slice_at_axis 21from skimage.color import (rgb2hsv, hsv2rgb, 22 rgb2xyz, xyz2rgb, 23 rgb2hed, hed2rgb, 24 separate_stains, 25 combine_stains, 26 rgb2rgbcie, rgbcie2rgb, 27 convert_colorspace, 28 rgb2gray, gray2rgb, 29 xyz2lab, lab2xyz, 30 lab2rgb, rgb2lab, 31 xyz2luv, luv2xyz, 32 luv2rgb, rgb2luv, 33 lab2lch, lch2lab, 34 rgb2yuv, yuv2rgb, 35 rgb2yiq, yiq2rgb, 36 rgb2ypbpr, ypbpr2rgb, 37 rgb2ycbcr, ycbcr2rgb, 38 rgb2ydbdr, ydbdr2rgb, 39 rgba2rgb, gray2rgba) 40from skimage.util import img_as_float, img_as_ubyte, img_as_float32 41 42 43class TestColorconv(): 44 45 img_rgb = data.colorwheel() 46 img_grayscale = data.camera() 47 img_rgba = np.array([[[0, 0.5, 1, 0], 48 [0, 0.5, 1, 1], 49 [0, 0.5, 1, 0.5]]]).astype(float) 50 img_stains = img_as_float(img_rgb) * 0.3 51 52 colbars = np.array([[1, 1, 0, 0, 1, 1, 0, 0], 53 [1, 1, 1, 1, 0, 0, 0, 0], 54 [1, 0, 1, 0, 1, 0, 1, 0]]).astype(float) 55 colbars_array = np.swapaxes(colbars.reshape(3, 4, 2), 0, 2) 56 colbars_point75 = colbars * 0.75 57 colbars_point75_array = np.swapaxes(colbars_point75.reshape(3, 4, 2), 0, 2) 58 59 xyz_array = np.array([[[0.4124, 0.21260, 0.01930]], # red 60 [[0, 0, 0]], # black 61 [[.9505, 1., 1.089]], # white 62 [[.1805, .0722, .9505]], # blue 63 [[.07719, .15438, .02573]], # green 64 ]) 65 lab_array = np.array([[[53.233, 80.109, 67.220]], # red 66 [[0., 0., 0.]], # black 67 [[100.0, 0.005, -0.010]], # white 68 [[32.303, 79.197, -107.864]], # blue 69 [[46.229, -51.7, 49.898]], # green 70 ]) 71 72 luv_array = np.array([[[53.233, 175.053, 37.751]], # red 73 [[0., 0., 0.]], # black 74 [[100., 0.001, -0.017]], # white 75 [[32.303, -9.400, -130.358]], # blue 76 [[46.228, -43.774, 56.589]], # green 77 ]) 78 79 # RGBA to RGB 80 @pytest.mark.parametrize("channel_axis", [0, 1, 2, -1, -2, -3]) 81 def test_rgba2rgb_conversion(self, channel_axis): 82 rgba = self.img_rgba 83 84 rgba = np.moveaxis(rgba, source=-1, destination=channel_axis) 85 rgb = rgba2rgb(rgba, channel_axis=channel_axis) 86 rgb = np.moveaxis(rgb, source=channel_axis, destination=-1) 87 88 expected = np.array([[[1, 1, 1], 89 [0, 0.5, 1], 90 [0.5, 0.75, 1]]]).astype(float) 91 assert_equal(rgb.shape, expected.shape) 92 assert_almost_equal(rgb, expected) 93 94 def test_rgba2rgb_error_grayscale(self): 95 with pytest.raises(ValueError): 96 rgba2rgb(self.img_grayscale) 97 98 @pytest.mark.parametrize("channel_axis", [None, 1.5]) 99 def test_rgba2rgb_error_channel_axis_invalid(self, channel_axis): 100 with pytest.raises(TypeError): 101 rgba2rgb(self.img_rgba, channel_axis=channel_axis) 102 103 @pytest.mark.parametrize("channel_axis", [-4, 3]) 104 def test_rgba2rgb_error_channel_axis_out_of_range(self, channel_axis): 105 with pytest.raises(np.AxisError): 106 rgba2rgb(self.img_rgba, channel_axis=channel_axis) 107 108 def test_rgba2rgb_error_rgb(self): 109 with pytest.raises(ValueError): 110 rgba2rgb(self.img_rgb) 111 112 def test_rgba2rgb_dtype(self): 113 rgba = self.img_rgba.astype('float64') 114 rgba32 = img_as_float32(rgba) 115 116 assert rgba2rgb(rgba).dtype == rgba.dtype 117 assert rgba2rgb(rgba32).dtype == rgba32.dtype 118 119 # RGB to HSV 120 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 121 def test_rgb2hsv_conversion(self, channel_axis): 122 rgb = img_as_float(self.img_rgb)[::16, ::16] 123 124 _rgb = np.moveaxis(rgb, source=-1, destination=channel_axis) 125 hsv = rgb2hsv(_rgb, channel_axis=channel_axis) 126 hsv = np.moveaxis(hsv, source=channel_axis, destination=-1) 127 hsv = hsv.reshape(-1, 3) 128 129 # ground truth from colorsys 130 gt = np.array([colorsys.rgb_to_hsv(pt[0], pt[1], pt[2]) 131 for pt in rgb.reshape(-1, 3)] 132 ) 133 assert_almost_equal(hsv, gt) 134 135 def test_rgb2hsv_error_grayscale(self): 136 with pytest.raises(ValueError): 137 rgb2hsv(self.img_grayscale) 138 139 def test_rgb2hsv_dtype(self): 140 rgb = img_as_float(self.img_rgb) 141 rgb32 = img_as_float32(self.img_rgb) 142 143 assert rgb2hsv(rgb).dtype == rgb.dtype 144 assert rgb2hsv(rgb32).dtype == rgb32.dtype 145 146 # HSV to RGB 147 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 148 def test_hsv2rgb_conversion(self, channel_axis): 149 rgb = self.img_rgb.astype("float32")[::16, ::16] 150 # create HSV image with colorsys 151 hsv = np.array([colorsys.rgb_to_hsv(pt[0], pt[1], pt[2]) 152 for pt in rgb.reshape(-1, 3)]).reshape(rgb.shape) 153 154 hsv = np.moveaxis(hsv, source=-1, destination=channel_axis) 155 _rgb = hsv2rgb(hsv, channel_axis=channel_axis) 156 _rgb = np.moveaxis(_rgb, source=channel_axis, destination=-1) 157 158 # convert back to RGB and compare with original. 159 # relative precision for RGB -> HSV roundtrip is about 1e-6 160 assert_almost_equal(rgb, _rgb, decimal=4) 161 162 def test_hsv2rgb_error_grayscale(self): 163 with pytest.raises(ValueError): 164 hsv2rgb(self.img_grayscale) 165 166 def test_hsv2rgb_dtype(self): 167 rgb = self.img_rgb.astype("float32")[::16, ::16] 168 # create HSV image with colorsys 169 hsv = np.array([colorsys.rgb_to_hsv(pt[0], pt[1], pt[2]) 170 for pt in rgb.reshape(-1, 3)], 171 dtype='float64').reshape(rgb.shape) 172 hsv32 = hsv.astype('float32') 173 174 assert hsv2rgb(hsv).dtype == hsv.dtype 175 assert hsv2rgb(hsv32).dtype == hsv32.dtype 176 177 # RGB to XYZ 178 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 179 def test_rgb2xyz_conversion(self, channel_axis): 180 gt = np.array([[[0.950456, 1. , 1.088754], 181 [0.538003, 0.787329, 1.06942 ], 182 [0.592876, 0.28484 , 0.969561], 183 [0.180423, 0.072169, 0.950227]], 184 [[0.770033, 0.927831, 0.138527], 185 [0.35758 , 0.71516 , 0.119193], 186 [0.412453, 0.212671, 0.019334], 187 [0. , 0. , 0. ]]]) 188 189 img = np.moveaxis( 190 self.colbars_array, source=-1, destination=channel_axis 191 ) 192 out = rgb2xyz(img, channel_axis=channel_axis) 193 out = np.moveaxis(out, source=channel_axis, destination=-1) 194 195 assert_almost_equal(out, gt) 196 197 # stop repeating the "raises" checks for all other functions that are 198 # implemented with color._convert() 199 def test_rgb2xyz_error_grayscale(self): 200 with pytest.raises(ValueError): 201 rgb2xyz(self.img_grayscale) 202 203 def test_rgb2xyz_dtype(self): 204 img = self.colbars_array 205 img32 = img.astype('float32') 206 207 assert rgb2xyz(img).dtype == img.dtype 208 assert rgb2xyz(img32).dtype == img32.dtype 209 210 # XYZ to RGB 211 def test_xyz2rgb_conversion(self): 212 assert_almost_equal(xyz2rgb(rgb2xyz(self.colbars_array)), 213 self.colbars_array) 214 215 def test_xyz2rgb_dtype(self): 216 img = rgb2xyz(self.colbars_array) 217 img32 = img.astype('float32') 218 219 assert xyz2rgb(img).dtype == img.dtype 220 assert xyz2rgb(img32).dtype == img32.dtype 221 222 # RGB<->XYZ roundtrip on another image 223 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 224 def test_xyz_rgb_roundtrip(self, channel_axis): 225 img_rgb = img_as_float(self.img_rgb) 226 227 img_rgb = np.moveaxis(img_rgb, source=-1, destination=channel_axis) 228 round_trip = xyz2rgb(rgb2xyz(img_rgb, channel_axis=channel_axis), 229 channel_axis=channel_axis) 230 231 assert_array_almost_equal(round_trip, img_rgb) 232 233 # HED<->RGB roundtrip with ubyte image 234 def test_hed_rgb_roundtrip(self): 235 img_in = img_as_ubyte(self.img_stains) 236 img_out = rgb2hed(hed2rgb(img_in)) 237 assert_equal(img_as_ubyte(img_out), img_in) 238 239 # HED<->RGB roundtrip with float image 240 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 241 def test_hed_rgb_float_roundtrip(self, channel_axis): 242 img_in = self.img_stains 243 img_in = np.moveaxis(img_in, source=-1, destination=channel_axis) 244 img_out = rgb2hed( 245 hed2rgb(img_in, channel_axis=channel_axis), 246 channel_axis=channel_axis 247 ) 248 assert_array_almost_equal(img_out, img_in) 249 250 # BRO<->RGB roundtrip with ubyte image 251 def test_bro_rgb_roundtrip(self): 252 from skimage.color.colorconv import bro_from_rgb, rgb_from_bro 253 img_in = img_as_ubyte(self.img_stains) 254 img_out = combine_stains(img_in, rgb_from_bro) 255 img_out = separate_stains(img_out, bro_from_rgb) 256 assert_equal(img_as_ubyte(img_out), img_in) 257 258 # BRO<->RGB roundtrip with float image 259 @pytest.mark.parametrize("channel_axis", [0, 1, -1]) 260 def test_bro_rgb_roundtrip_float(self, channel_axis): 261 from skimage.color.colorconv import bro_from_rgb, rgb_from_bro 262 img_in = self.img_stains 263 img_in = np.moveaxis(img_in, source=-1, destination=channel_axis) 264 img_out = combine_stains( 265 img_in, rgb_from_bro, channel_axis=channel_axis 266 ) 267 img_out = separate_stains( 268 img_out, bro_from_rgb, channel_axis=channel_axis 269 ) 270 assert_array_almost_equal(img_out, img_in) 271 272 # RGB to RGB CIE 273 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 274 def test_rgb2rgbcie_conversion(self, channel_axis): 275 gt = np.array([[[ 0.1488856 , 0.18288098, 0.19277574], 276 [ 0.01163224, 0.16649536, 0.18948516], 277 [ 0.12259182, 0.03308008, 0.17298223], 278 [-0.01466154, 0.01669446, 0.16969164]], 279 [[ 0.16354714, 0.16618652, 0.0230841 ], 280 [ 0.02629378, 0.1498009 , 0.01979351], 281 [ 0.13725336, 0.01638562, 0.00329059], 282 [ 0. , 0. , 0. ]]]) 283 284 img = np.moveaxis( 285 self.colbars_array, source=-1, destination=channel_axis 286 ) 287 out = rgb2rgbcie(img, channel_axis=channel_axis) 288 289 out = np.moveaxis(out, source=channel_axis, destination=-1) 290 291 assert_almost_equal(out, gt) 292 293 def test_rgb2rgbcie_dtype(self): 294 img = self.colbars_array.astype('float64') 295 img32 = img.astype('float32') 296 297 assert rgb2rgbcie(img).dtype == img.dtype 298 assert rgb2rgbcie(img32).dtype == img32.dtype 299 300 # RGB CIE to RGB 301 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 302 def test_rgbcie2rgb_conversion(self, channel_axis): 303 rgb = np.moveaxis( 304 self.colbars_array, source=-1, destination=channel_axis 305 ) 306 round_trip = rgbcie2rgb(rgb2rgbcie(rgb, channel_axis=channel_axis), 307 channel_axis=channel_axis) 308 # only roundtrip test, we checked rgb2rgbcie above already 309 assert_almost_equal(round_trip, rgb) 310 311 def test_rgbcie2rgb_dtype(self): 312 img = rgb2rgbcie(self.colbars_array).astype('float64') 313 img32 = img.astype('float32') 314 315 assert rgbcie2rgb(img).dtype == img.dtype 316 assert rgbcie2rgb(img32).dtype == img32.dtype 317 318 @pytest.mark.parametrize("channel_axis", [0, -1]) 319 def test_convert_colorspace(self, channel_axis): 320 colspaces = ['HSV', 'RGB CIE', 'XYZ', 'YCbCr', 'YPbPr', 'YDbDr'] 321 colfuncs_from = [ 322 hsv2rgb, rgbcie2rgb, xyz2rgb, 323 ycbcr2rgb, ypbpr2rgb, ydbdr2rgb 324 ] 325 colfuncs_to = [ 326 rgb2hsv, rgb2rgbcie, rgb2xyz, 327 rgb2ycbcr, rgb2ypbpr, rgb2ydbdr 328 ] 329 330 colbars_array = np.moveaxis( 331 self.colbars_array, source=-1, destination=channel_axis 332 ) 333 334 kw = dict(channel_axis=channel_axis) 335 336 assert_almost_equal( 337 convert_colorspace(colbars_array, 'RGB', 'RGB', **kw), 338 colbars_array) 339 340 for i, space in enumerate(colspaces): 341 gt = colfuncs_from[i](colbars_array, **kw) 342 assert_almost_equal( 343 convert_colorspace(colbars_array, space, 'RGB', **kw), gt) 344 gt = colfuncs_to[i](colbars_array, **kw) 345 assert_almost_equal( 346 convert_colorspace(colbars_array, 'RGB', space, **kw), gt) 347 348 with pytest.raises(ValueError): 349 convert_colorspace(self.colbars_array, 'nokey', 'XYZ') 350 with pytest.raises(ValueError): 351 convert_colorspace(self.colbars_array, 'RGB', 'nokey') 352 353 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 354 def test_rgb2gray(self, channel_axis): 355 x = np.array([1, 1, 1]).reshape((1, 1, 3)).astype(float) 356 x = np.moveaxis(x, source=-1, destination=channel_axis) 357 g = rgb2gray(x, channel_axis=channel_axis) 358 assert_array_almost_equal(g, 1) 359 360 assert_equal(g.shape, (1, 1)) 361 362 def test_rgb2gray_contiguous(self): 363 x = np.random.rand(10, 10, 3) 364 assert rgb2gray(x).flags["C_CONTIGUOUS"] 365 assert rgb2gray(x[:5, :5]).flags["C_CONTIGUOUS"] 366 367 def test_rgb2gray_alpha(self): 368 x = np.empty((10, 10, 4)) 369 with pytest.raises(ValueError): 370 rgb2gray(x) 371 372 def test_rgb2gray_on_gray(self): 373 with pytest.raises(ValueError): 374 rgb2gray(np.empty((5, 5))) 375 376 def test_rgb2gray_dtype(self): 377 img = np.random.rand(10, 10, 3).astype('float64') 378 img32 = img.astype('float32') 379 380 assert rgb2gray(img).dtype == img.dtype 381 assert rgb2gray(img32).dtype == img32.dtype 382 383 # test matrices for xyz2lab and lab2xyz generated using 384 # http://www.easyrgb.com/index.php?X=CALC 385 # Note: easyrgb website displays xyz*100 386 def test_xyz2lab(self): 387 assert_array_almost_equal(xyz2lab(self.xyz_array), 388 self.lab_array, decimal=3) 389 390 # Test the conversion with the rest of the illuminants. 391 for I in ["A", "B", "C", "d50", "d55", "d65"]: 392 I = I.lower() 393 for obs in ["2", "10", "R"]: 394 obs = obs.lower() 395 fname = f'color/tests/data/lab_array_{I}_{obs}.npy' 396 lab_array_I_obs = np.load(fetch(fname)) 397 assert_array_almost_equal(lab_array_I_obs, 398 xyz2lab(self.xyz_array, I, obs), 399 decimal=2) 400 for I in ["d75", "e"]: 401 fname = f'color/tests/data/lab_array_{I}_2.npy' 402 lab_array_I_obs = np.load(fetch(fname)) 403 assert_array_almost_equal(lab_array_I_obs, 404 xyz2lab(self.xyz_array, I, "2"), 405 decimal=2) 406 407 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 408 def test_xyz2lab_channel_axis(self, channel_axis): 409 # test conversion with channels along a specified axis 410 xyz = np.moveaxis(self.xyz_array, source=-1, destination=channel_axis) 411 lab = xyz2lab(xyz, channel_axis=channel_axis) 412 lab = np.moveaxis(lab, source=channel_axis, destination=-1) 413 assert_array_almost_equal(lab, self.lab_array, decimal=3) 414 415 def test_xyz2lab_dtype(self): 416 img = self.xyz_array.astype('float64') 417 img32 = img.astype('float32') 418 419 assert xyz2lab(img).dtype == img.dtype 420 assert xyz2lab(img32).dtype == img32.dtype 421 422 def test_lab2xyz(self): 423 assert_array_almost_equal(lab2xyz(self.lab_array), 424 self.xyz_array, decimal=3) 425 426 # Test the conversion with the rest of the illuminants. 427 for I in ["A", "B", "C", "d50", "d55", "d65"]: 428 I = I.lower() 429 for obs in ["2", "10", "R"]: 430 obs = obs.lower() 431 fname = f'color/tests/data/lab_array_{I}_{obs}.npy' 432 lab_array_I_obs = np.load(fetch(fname)) 433 assert_array_almost_equal(lab2xyz(lab_array_I_obs, I, obs), 434 self.xyz_array, decimal=3) 435 for I in ["d75", "e"]: 436 fname = f'color/tests/data/lab_array_{I}_2.npy' 437 lab_array_I_obs = np.load(fetch(fname)) 438 assert_array_almost_equal(lab2xyz(lab_array_I_obs, I, "2"), 439 self.xyz_array, decimal=3) 440 441 # And we include a call to test the exception handling in the code. 442 with pytest.raises(ValueError): 443 lab2xyz(lab_array_I_obs, "NaI", "2") # Not an illuminant 444 445 with pytest.raises(ValueError): 446 lab2xyz(lab_array_I_obs, "d50", "42") # Not a degree 447 448 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 449 def test_lab2xyz_channel_axis(self, channel_axis): 450 # test conversion with channels along a specified axis 451 lab = np.moveaxis(self.lab_array, source=-1, destination=channel_axis) 452 xyz = lab2xyz(lab, channel_axis=channel_axis) 453 xyz = np.moveaxis(xyz, source=channel_axis, destination=-1) 454 assert_array_almost_equal(xyz, self.xyz_array, decimal=3) 455 456 def test_lab2xyz_dtype(self): 457 img = self.lab_array.astype('float64') 458 img32 = img.astype('float32') 459 460 assert lab2xyz(img).dtype == img.dtype 461 assert lab2xyz(img32).dtype == img32.dtype 462 463 def test_rgb2lab_brucelindbloom(self): 464 """ 465 Test the RGB->Lab conversion by comparing to the calculator on the 466 authoritative Bruce Lindbloom 467 [website](http://brucelindbloom.com/index.html?ColorCalculator.html). 468 """ 469 # Obtained with D65 white point, sRGB model and gamma 470 gt_for_colbars = np.array([ 471 [100, 0, 0], 472 [97.1393, -21.5537, 94.4780], 473 [91.1132, -48.0875, -14.1312], 474 [87.7347, -86.1827, 83.1793], 475 [60.3242, 98.2343, -60.8249], 476 [53.2408, 80.0925, 67.2032], 477 [32.2970, 79.1875, -107.8602], 478 [0, 0, 0]]).T 479 gt_array = np.swapaxes(gt_for_colbars.reshape(3, 4, 2), 0, 2) 480 assert_array_almost_equal( 481 rgb2lab(self.colbars_array), gt_array, decimal=2 482 ) 483 484 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 485 def test_lab_rgb_roundtrip(self, channel_axis): 486 img_rgb = img_as_float(self.img_rgb) 487 img_rgb = np.moveaxis(img_rgb, source=-1, destination=channel_axis) 488 assert_array_almost_equal( 489 lab2rgb( 490 rgb2lab(img_rgb, channel_axis=channel_axis), 491 channel_axis=channel_axis 492 ), 493 img_rgb, 494 ) 495 496 def test_rgb2lab_dtype(self): 497 img = self.colbars_array.astype('float64') 498 img32 = img.astype('float32') 499 500 assert rgb2lab(img).dtype == img.dtype 501 assert rgb2lab(img32).dtype == img32.dtype 502 503 def test_lab2rgb_dtype(self): 504 img = self.lab_array.astype('float64') 505 img32 = img.astype('float32') 506 507 assert lab2rgb(img).dtype == img.dtype 508 assert lab2rgb(img32).dtype == img32.dtype 509 510 # test matrices for xyz2luv and luv2xyz generated using 511 # http://www.easyrgb.com/index.php?X=CALC 512 # Note: easyrgb website displays xyz*100 513 def test_xyz2luv(self): 514 assert_array_almost_equal(xyz2luv(self.xyz_array), 515 self.luv_array, decimal=3) 516 517 # Test the conversion with the rest of the illuminants. 518 for I in ["A", "B", "C", "d50", "d55", "d65"]: 519 I = I.lower() 520 for obs in ["2", "10", "R"]: 521 obs = obs.lower() 522 fname = f'color/tests/data/luv_array_{I}_{obs}.npy' 523 luv_array_I_obs = np.load(fetch(fname)) 524 assert_array_almost_equal(luv_array_I_obs, 525 xyz2luv(self.xyz_array, I, obs), 526 decimal=2) 527 for I in ["d75", "e"]: 528 fname = f'color/tests/data/luv_array_{I}_2.npy' 529 luv_array_I_obs = np.load(fetch(fname)) 530 assert_array_almost_equal(luv_array_I_obs, 531 xyz2luv(self.xyz_array, I, "2"), 532 decimal=2) 533 534 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 535 def test_xyz2luv_channel_axis(self, channel_axis): 536 # test conversion with channels along a specified axis 537 xyz = np.moveaxis(self.xyz_array, source=-1, destination=channel_axis) 538 luv = xyz2luv(xyz, channel_axis=channel_axis) 539 luv = np.moveaxis(luv, source=channel_axis, destination=-1) 540 assert_array_almost_equal(luv, self.luv_array, decimal=3) 541 542 def test_xyz2luv_dtype(self): 543 img = self.xyz_array.astype('float64') 544 img32 = img.astype('float32') 545 546 assert xyz2luv(img).dtype == img.dtype 547 assert xyz2luv(img32).dtype == img32.dtype 548 549 def test_luv2xyz(self): 550 assert_array_almost_equal(luv2xyz(self.luv_array), 551 self.xyz_array, decimal=3) 552 553 # Test the conversion with the rest of the illuminants. 554 for I in ["A", "B", "C", "d50", "d55", "d65"]: 555 I = I.lower() 556 for obs in ["2", "10", "R"]: 557 obs = obs.lower() 558 fname = f'color/tests/data/luv_array_{I}_{obs}.npy' 559 luv_array_I_obs = np.load(fetch(fname)) 560 assert_array_almost_equal(luv2xyz(luv_array_I_obs, I, obs), 561 self.xyz_array, decimal=3) 562 for I in ["d75", "e"]: 563 fname = f'color/tests/data/luv_array_{I}_2.npy' 564 luv_array_I_obs = np.load(fetch(fname)) 565 assert_array_almost_equal(luv2xyz(luv_array_I_obs, I, "2"), 566 self.xyz_array, decimal=3) 567 568 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 569 def test_luv2xyz_channel_axis(self, channel_axis): 570 # test conversion with channels along a specified axis 571 luv = np.moveaxis(self.luv_array, source=-1, destination=channel_axis) 572 xyz = luv2xyz(luv, channel_axis=channel_axis) 573 xyz = np.moveaxis(xyz, source=channel_axis, destination=-1) 574 assert_array_almost_equal(xyz, self.xyz_array, decimal=3) 575 576 def test_luv2xyz_dtype(self): 577 img = self.luv_array.astype('float64') 578 img32 = img.astype('float32') 579 580 assert luv2xyz(img).dtype == img.dtype 581 assert luv2xyz(img32).dtype == img32.dtype 582 583 def test_rgb2luv_brucelindbloom(self): 584 """ 585 Test the RGB->Lab conversion by comparing to the calculator on the 586 authoritative Bruce Lindbloom 587 [website](http://brucelindbloom.com/index.html?ColorCalculator.html). 588 """ 589 # Obtained with D65 white point, sRGB model and gamma 590 gt_for_colbars = np.array([ 591 [100, 0, 0], 592 [97.1393, 7.7056, 106.7866], 593 [91.1132, -70.4773, -15.2042], 594 [87.7347, -83.0776, 107.3985], 595 [60.3242, 84.0714, -108.6834], 596 [53.2408, 175.0151, 37.7564], 597 [32.2970, -9.4054, -130.3423], 598 [0, 0, 0]]).T 599 gt_array = np.swapaxes(gt_for_colbars.reshape(3, 4, 2), 0, 2) 600 assert_array_almost_equal(rgb2luv(self.colbars_array), 601 gt_array, decimal=2) 602 603 def test_rgb2luv_dtype(self): 604 img = self.colbars_array.astype('float64') 605 img32 = img.astype('float32') 606 607 assert rgb2luv(img).dtype == img.dtype 608 assert rgb2luv(img32).dtype == img32.dtype 609 610 def test_luv2rgb_dtype(self): 611 img = self.luv_array.astype('float64') 612 img32 = img.astype('float32') 613 614 assert luv2rgb(img).dtype == img.dtype 615 assert luv2rgb(img32).dtype == img32.dtype 616 617 @pytest.mark.parametrize("channel_axis", [0, 1, -1 -2]) 618 def test_luv_rgb_roundtrip(self, channel_axis): 619 img_rgb = img_as_float(self.img_rgb) 620 img_rgb = np.moveaxis(img_rgb, source=-1, destination=channel_axis) 621 assert_array_almost_equal( 622 luv2rgb( 623 rgb2luv(img_rgb, channel_axis=channel_axis), 624 channel_axis=channel_axis 625 ), 626 img_rgb, 627 ) 628 629 def test_lab_rgb_outlier(self): 630 lab_array = np.ones((3, 1, 3)) 631 lab_array[0] = [50, -12, 85] 632 lab_array[1] = [50, 12, -85] 633 lab_array[2] = [90, -4, -47] 634 rgb_array = np.array([[[0.501, 0.481, 0]], 635 [[0, 0.482, 1.]], 636 [[0.578, 0.914, 1.]], 637 ]) 638 assert_almost_equal(lab2rgb(lab_array), rgb_array, decimal=3) 639 640 def test_lab_full_gamut(self): 641 a, b = np.meshgrid(np.arange(-100, 100), np.arange(-100, 100)) 642 L = np.ones(a.shape) 643 lab = np.dstack((L, a, b)) 644 for value in [0, 10, 20]: 645 lab[:, :, 0] = value 646 with expected_warnings(['Color data out of range']): 647 lab2xyz(lab) 648 649 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 650 def test_lab_lch_roundtrip(self, channel_axis): 651 rgb = img_as_float(self.img_rgb) 652 rgb = np.moveaxis(rgb, source=-1, destination=channel_axis) 653 lab = rgb2lab(rgb, channel_axis=channel_axis) 654 lab2 = lch2lab( 655 lab2lch(lab, channel_axis=channel_axis), 656 channel_axis=channel_axis, 657 ) 658 assert_array_almost_equal(lab2, lab) 659 660 def test_rgb_lch_roundtrip(self): 661 rgb = img_as_float(self.img_rgb) 662 lab = rgb2lab(rgb) 663 lch = lab2lch(lab) 664 lab2 = lch2lab(lch) 665 rgb2 = lab2rgb(lab2) 666 assert_array_almost_equal(rgb, rgb2) 667 668 def test_lab_lch_0d(self): 669 lab0 = self._get_lab0() 670 lch0 = lab2lch(lab0) 671 lch2 = lab2lch(lab0[None, None, :]) 672 assert_array_almost_equal(lch0, lch2[0, 0, :]) 673 674 def test_lab_lch_1d(self): 675 lab0 = self._get_lab0() 676 lch0 = lab2lch(lab0) 677 lch1 = lab2lch(lab0[None, :]) 678 assert_array_almost_equal(lch0, lch1[0, :]) 679 680 def test_lab_lch_3d(self): 681 lab0 = self._get_lab0() 682 lch0 = lab2lch(lab0) 683 lch3 = lab2lch(lab0[None, None, None, :]) 684 assert_array_almost_equal(lch0, lch3[0, 0, 0, :]) 685 686 def _get_lab0(self): 687 rgb = img_as_float(self.img_rgb[:1, :1, :]) 688 return rgb2lab(rgb)[0, 0, :] 689 690 def test_yuv(self): 691 rgb = np.array([[[1.0, 1.0, 1.0]]]) 692 assert_array_almost_equal(rgb2yuv(rgb), np.array([[[1, 0, 0]]])) 693 assert_array_almost_equal(rgb2yiq(rgb), np.array([[[1, 0, 0]]])) 694 assert_array_almost_equal(rgb2ypbpr(rgb), np.array([[[1, 0, 0]]])) 695 assert_array_almost_equal( 696 rgb2ycbcr(rgb), np.array([[[235, 128, 128]]]) 697 ) 698 assert_array_almost_equal(rgb2ydbdr(rgb), np.array([[[1, 0, 0]]])) 699 rgb = np.array([[[0.0, 1.0, 0.0]]]) 700 assert_array_almost_equal( 701 rgb2yuv(rgb), np.array([[[0.587, -0.28886916, -0.51496512]]]) 702 ) 703 assert_array_almost_equal( 704 rgb2yiq(rgb), np.array([[[0.587, -0.27455667, -0.52273617]]]) 705 ) 706 assert_array_almost_equal( 707 rgb2ypbpr(rgb), np.array([[[0.587, -0.331264, -0.418688]]]) 708 ) 709 assert_array_almost_equal( 710 rgb2ycbcr(rgb), np.array([[[144.553, 53.797, 34.214]]]) 711 ) 712 assert_array_almost_equal( 713 rgb2ydbdr(rgb), np.array([[[0.587, -0.883, 1.116]]]) 714 ) 715 716 @pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 717 def test_yuv_roundtrip(self, channel_axis): 718 img_rgb = img_as_float(self.img_rgb)[::16, ::16] 719 img_rgb = np.moveaxis(img_rgb, source=-1, destination=channel_axis) 720 assert_array_almost_equal( 721 yuv2rgb(rgb2yuv(img_rgb, channel_axis=channel_axis), 722 channel_axis=channel_axis), 723 img_rgb) 724 assert_array_almost_equal( 725 yiq2rgb(rgb2yiq(img_rgb, channel_axis=channel_axis), 726 channel_axis=channel_axis), 727 img_rgb) 728 assert_array_almost_equal( 729 ypbpr2rgb(rgb2ypbpr(img_rgb, channel_axis=channel_axis), 730 channel_axis=channel_axis), 731 img_rgb) 732 assert_array_almost_equal( 733 ycbcr2rgb(rgb2ycbcr(img_rgb, channel_axis=channel_axis), 734 channel_axis=channel_axis), 735 img_rgb) 736 assert_array_almost_equal( 737 ydbdr2rgb(rgb2ydbdr(img_rgb, channel_axis=channel_axis), 738 channel_axis=channel_axis), 739 img_rgb) 740 741 def test_rgb2yuv_dtype(self): 742 img = self.colbars_array.astype('float64') 743 img32 = img.astype('float32') 744 745 assert rgb2yuv(img).dtype == img.dtype 746 assert rgb2yuv(img32).dtype == img32.dtype 747 748 def test_yuv2rgb_dtype(self): 749 img = rgb2yuv(self.colbars_array).astype('float64') 750 img32 = img.astype('float32') 751 752 assert yuv2rgb(img).dtype == img.dtype 753 assert yuv2rgb(img32).dtype == img32.dtype 754 755 def test_rgb2yiq_conversion(self): 756 rgb = img_as_float(self.img_rgb)[::16, ::16] 757 yiq = rgb2yiq(rgb).reshape(-1, 3) 758 gt = np.array([colorsys.rgb_to_yiq(pt[0], pt[1], pt[2]) 759 for pt in rgb.reshape(-1, 3)] 760 ) 761 assert_almost_equal(yiq, gt, decimal=2) 762 763 764def test_gray2rgb(): 765 x = np.array([0, 0.5, 1]) 766 w = gray2rgb(x) 767 expected_output = np.array([[ 0, 0, 0 ], 768 [ 0.5, 0.5, 0.5, ], 769 [ 1, 1, 1 ]]) 770 771 assert_equal(w, expected_output) 772 773 x = x.reshape((3, 1)) 774 y = gray2rgb(x) 775 776 assert_equal(y.shape, (3, 1, 3)) 777 assert_equal(y.dtype, x.dtype) 778 assert_equal(y[..., 0], x) 779 assert_equal(y[0, 0, :], [0, 0, 0]) 780 781 x = np.array([[0, 128, 255]], dtype=np.uint8) 782 z = gray2rgb(x) 783 784 assert_equal(z.shape, (1, 3, 3)) 785 assert_equal(z[..., 0], x) 786 assert_equal(z[0, 1, :], [128, 128, 128]) 787 788 789def test_gray2rgb_rgb(): 790 x = np.random.rand(5, 5, 4) 791 y = gray2rgb(x) 792 assert y.shape == (x.shape + (3,)) 793 for i in range(3): 794 assert_equal(x, y[..., i]) 795 796 797@pytest.mark.parametrize("shape", [(5, 5), (5, 5, 4), (5, 4, 5, 4)]) 798@pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 799def test_gray2rgba(shape, channel_axis): 800 # nD case 801 img = np.random.random(shape) 802 rgba = gray2rgba(img, channel_axis=channel_axis) 803 assert rgba.ndim == img.ndim + 1 804 805 # Shape check 806 new_axis_loc = channel_axis % rgba.ndim 807 assert_equal(rgba.shape, 808 shape[:new_axis_loc] + (4, ) + shape[new_axis_loc:]) 809 810 # dtype check 811 assert rgba.dtype == img.dtype 812 813 # RGB channels check 814 for channel in range(3): 815 assert_equal(rgba[slice_at_axis(channel, axis=new_axis_loc)], img) 816 817 # Alpha channel check 818 assert_equal(rgba[slice_at_axis(3, axis=new_axis_loc)], 1.0) 819 820 821@pytest.mark.parametrize("shape", [(5, 5), (5, 5, 4), (5, 4, 5, 4)]) 822@pytest.mark.parametrize("channel_axis", [0, 1, -1, -2]) 823def test_gray2rgb_channel_axis(shape, channel_axis): 824 # nD case 825 img = np.random.random(shape) 826 rgb = gray2rgb(img, channel_axis=channel_axis) 827 assert rgb.ndim == img.ndim + 1 828 829 # Shape check 830 new_axis_loc = channel_axis % rgb.ndim 831 assert_equal(rgb.shape, 832 shape[:new_axis_loc] + (3, ) + shape[new_axis_loc:]) 833 834 # dtype check 835 assert rgb.dtype == img.dtype 836 837 838def test_gray2rgba_dtype(): 839 img_f64 = np.random.random((5, 5)) 840 img_f32 = img_f64.astype('float32') 841 img_u8 = img_as_ubyte(img_f64) 842 img_int = img_u8.astype(int) 843 844 for img in [img_f64, img_f32, img_u8, img_int]: 845 assert gray2rgba(img).dtype == img.dtype 846 847 848def test_gray2rgba_alpha(): 849 img = np.random.random((5, 5)) 850 img_u8 = img_as_ubyte(img) 851 852 # Default 853 alpha = None 854 rgba = gray2rgba(img, alpha) 855 856 assert_equal(rgba[..., :3], gray2rgb(img)) 857 assert_equal(rgba[..., 3], 1.0) 858 859 # Scalar 860 alpha = 0.5 861 rgba = gray2rgba(img, alpha) 862 863 assert_equal(rgba[..., :3], gray2rgb(img)) 864 assert_equal(rgba[..., 3], alpha) 865 866 # Array 867 alpha = np.random.random((5, 5)) 868 rgba = gray2rgba(img, alpha) 869 870 assert_equal(rgba[..., :3], gray2rgb(img)) 871 assert_equal(rgba[..., 3], alpha) 872 873 # Warning about alpha cast 874 alpha = 0.5 875 with expected_warnings(["alpha cannot be safely cast to image dtype"]): 876 rgba = gray2rgba(img_u8, alpha) 877 assert_equal(rgba[..., :3], gray2rgb(img_u8)) 878 879 # Invalid shape 880 alpha = np.random.random((5, 5, 1)) 881 expected_err_msg = ("alpha.shape must match image.shape") 882 883 with pytest.raises(ValueError) as err: 884 rgba = gray2rgba(img, alpha) 885 assert expected_err_msg == str(err.value) 886 887 888@pytest.mark.parametrize("func", [rgb2gray, gray2rgb, gray2rgba]) 889@pytest.mark.parametrize("shape", ([(3, ), (2, 3), (4, 5, 3), (5, 4, 5, 3), 890 (4, 5, 4, 5, 3)])) 891def test_nD_gray_conversion(func, shape): 892 img = np.random.rand(*shape) 893 out = func(img) 894 common_ndim = min(out.ndim, len(shape)) 895 assert out.shape[:common_ndim] == shape[:common_ndim] 896 897 898@pytest.mark.parametrize("func", [rgb2hsv, hsv2rgb, 899 rgb2xyz, xyz2rgb, 900 rgb2hed, hed2rgb, 901 rgb2rgbcie, rgbcie2rgb, 902 xyz2lab, lab2xyz, 903 lab2rgb, rgb2lab, 904 xyz2luv, luv2xyz, 905 luv2rgb, rgb2luv, 906 lab2lch, lch2lab, 907 rgb2yuv, yuv2rgb, 908 rgb2yiq, yiq2rgb, 909 rgb2ypbpr, ypbpr2rgb, 910 rgb2ycbcr, ycbcr2rgb, 911 rgb2ydbdr, ydbdr2rgb]) 912@pytest.mark.parametrize("shape", ([(3, ), (2, 3), (4, 5, 3), (5, 4, 5, 3), 913 (4, 5, 4, 5, 3)])) 914def test_nD_color_conversion(func, shape): 915 img = np.random.rand(*shape) 916 out = func(img) 917 918 assert out.shape == img.shape 919 920 921@pytest.mark.parametrize("shape", ([(4, ), (2, 4), (4, 5, 4), (5, 4, 5, 4), 922 (4, 5, 4, 5, 4)])) 923def test_rgba2rgb_nD(shape): 924 img = np.random.rand(*shape) 925 out = rgba2rgb(img) 926 927 expected_shape = shape[:-1] + (3, ) 928 929 assert out.shape == expected_shape 930 931 932@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64]) 933def test_rgba2rgb_dtypes(dtype): 934 rgba = np.array([[[0, 0.5, 1, 0], 935 [0, 0.5, 1, 1], 936 [0, 0.5, 1, 0.5]]]).astype(dtype=dtype) 937 rgb = rgba2rgb(rgba) 938 float_dtype = _supported_float_type(rgba.dtype) 939 assert rgb.dtype == float_dtype 940 expected = np.array([[[1, 1, 1], 941 [0, 0.5, 1], 942 [0.5, 0.75, 1]]]).astype(float) 943 assert rgb.shape == expected.shape 944 assert_almost_equal(rgb, expected) 945 946 947@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64]) 948def test_lab_lch_roundtrip_dtypes(dtype): 949 rgb = img_as_float(data.colorwheel()).astype(dtype=dtype, copy=False) 950 lab = rgb2lab(rgb) 951 float_dtype = _supported_float_type(dtype) 952 assert lab.dtype == float_dtype 953 lab2 = lch2lab(lab2lch(lab)) 954 decimal = 4 if float_dtype == np.float32 else 7 955 assert_array_almost_equal(lab2, lab, decimal=decimal) 956 957 958@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64]) 959def test_rgb2hsv_dtypes(dtype): 960 rgb = img_as_float(data.colorwheel())[::16, ::16] 961 rgb = rgb.astype(dtype=dtype, copy=False) 962 hsv = rgb2hsv(rgb).reshape(-1, 3) 963 float_dtype = _supported_float_type(dtype) 964 assert hsv.dtype == float_dtype 965 # ground truth from colorsys 966 gt = np.array([colorsys.rgb_to_hsv(pt[0], pt[1], pt[2]) 967 for pt in rgb.reshape(-1, 3)] 968 ) 969 decimal = 3 if float_dtype == np.float32 else 7 970 assert_array_almost_equal(hsv, gt, decimal=decimal) 971