def test_speed(N=2): numpy.random.seed(1) osa = colorio.OsaUcs() cielab = colorio.CIELAB() # cam16 = colorio.CAM16(0.69, 20, L_A=64 / numpy.pi / 5) ciecam02 = colorio.CIECAM02(0.69, 20, L_A=64 / numpy.pi / 5) # This close probably means that another figure hasn't been properly closed. import matplotlib.pyplot as plt plt.close() perfplot.show( # Don't use numpy.random.rand(3, n) to avoid the CIECAM breakdown setup=lambda n: numpy.outer(numpy.random.rand(3), numpy.ones(n)) * 10, equality_check=None, kernels=[ osa.to_xyz100, cielab.to_xyz100, # cam16.to_xyz100, lambda Jsh: ciecam02.to_xyz100(Jsh, "Jsh"), numpy.cbrt, ], labels=["OSA-UCS", "CIELAB", "CIECAM02", "cbrt"], n_range=[2 ** n for n in range(N)], logx=True, logy=True, # relative_to=3 )
def test_reference_xyz_d50(xyz100, ref): cielab = colorio.CIELAB( whitepoint=colorio.illuminants.whitepoints_cie1931["D50"]) xyz100 = numpy.array(xyz100) assert numpy.all( abs(cielab.from_xyz100(xyz100) - ref) < 1.0e-4 * abs(numpy.array(ref))) return
def test_reference_xyz(xyz100, ref): cielab = colorio.CIELAB() xyz100 = numpy.array(xyz100) assert numpy.all( abs(cielab.from_xyz100(xyz100) - ref) < 1.0e-4 * abs(numpy.array(ref)) ) return
def test_speed(N=2): numpy.random.seed(1) osa = colorio.OsaUcs() cielab = colorio.CIELAB() # cam16 = colorio.CAM16(0.69, 20, L_A=64 / numpy.pi / 5) ciecam02 = colorio.CIECAM02(0.69, 20, L_A=64 / numpy.pi / 5) perfplot.plot( # Don't use numpy.random.rand(3, n) to avoid the CIECAM breakdown setup=lambda n: numpy.outer(numpy.random.rand(3), numpy.ones(n)) * 10, equality_check=None, kernels=[ osa.to_xyz100, cielab.to_xyz100, # cam16.to_xyz100, lambda Jsh: ciecam02.to_xyz100(Jsh, "Jsh"), numpy.cbrt, ], labels=["OSA-UCS", "CIELAB", "CIECAM02", "cbrt"], n_range=[2**n for n in range(N)], logx=True, logy=True, # relative_to=3 )
def test_show_straights(cs=colorio.CIELAB()): colorio.show_straights(cs) return
import numpy import pytest import colorio @pytest.mark.parametrize( "colorspace, cut_000", [ (colorio.CIELAB(), False), # (colorio.XYY(), True), (colorio.CAM02("UCS", 0.69, 20, 64 / numpy.pi / 5), False), ], ) def test_srgb_gamut(colorspace, cut_000, n=10): colorspace.save_srgb_gamut("srgb.vtu", n=n, cut_000=cut_000) return @pytest.mark.parametrize( "colorspace", [ colorio.CIELAB(), colorio.XYY(), colorio.CAM02("UCS", 0.69, 20, 64 / numpy.pi / 5), ], ) def test_cone_gamut(colorspace, n=10): observer = colorio.observers.cie_1931_2() colorspace.save_cone_gamut("cone.vtu", observer, max_Y=1) return
@pytest.mark.parametrize( 'colorspace, cut_000', [ # colorio.CIELAB(), (colorio.XYY(), True), (colorio.CAM02('UCS', 0.69, 20, 64 / numpy.pi / 5), False), ]) def test_srgb_gamut(colorspace, cut_000, n=10): colorio.show_srgb_gamut(colorspace, 'srgb.vtu', n=n, cut_000=cut_000) return @pytest.mark.parametrize('colorspace', [ colorio.CIELAB(), colorio.CAM02('UCS', 0.69, 20, 64 / numpy.pi / 5), ]) def test_hdr_gamut(colorspace, n=10): colorio.show_hdr_gamut(colorspace, 'hdr.vtu', n=n) return @pytest.mark.parametrize( 'colorspace,cut_000', [ # (colorio.CIELAB(), False), (colorio.XYY(), True), (colorio.CAM02('UCS', 0.69, 20, 64 / numpy.pi / 5), False), ]) def test_visible_gamut(colorspace, cut_000):
def test_conversion(xyz): cielab = colorio.CIELAB() out = cielab.to_xyz100(cielab.from_xyz100(xyz)) assert numpy.all(abs(xyz - out) < 1.0e-14) return
import pytest import colorio @pytest.mark.parametrize( "cs,k0,level", [ [colorio.XYY(), 2, 0.4], [colorio.CIELAB(), 0, 50], [colorio.CAM16UCS(0.69, 20, 4.074), 0, 50], ], ) def test_visible_slice(cs, k0, level): cs.show_visible_slice(k0, level) # cs.save_visible_slice("visible-slice.png", k0, level) return @pytest.mark.parametrize( "cs,k0,level", [[colorio.XYY(), 2, 0.4], [colorio.CIELUV(), 0, 50], [colorio.JzAzBz(), 0, 0.5]], ) def test_macadam(cs, k0, level): cs.show_macadam(k0, level) cs.save_macadam("macadam.png", k0, level) return @pytest.mark.parametrize(
def get_srgb1(z, alpha=1, colorspace="CAM16"): assert alpha >= 0 # A number of scalings f that map the magnitude [0, infty] to [0, 1] are possible. # One desirable property is # (1) f(1/r) = 1 - f(r). # This makes sure that the representation of the inverse of a function is exactly as # light as the original function is dark. The function g_a(r) = 1 - a^|r| (with some # 0 < a < 1), as it is sometimes suggested (e.g., on Wikipedia # <https://en.wikipedia.org/wiki/Domain_coloring>) does _not_ fulfill (1). The # function 2/pi * arctan(r) is _very_ close to g_(1/2) between 0 and 1 and has that # property, so this is good alternative. Here, we are using the simple r^a / r^a+1 # with a configurable parameter a. def abs_scaling(r): # Fulfills (1) for any alpha >= 0 return r**alpha / (r**alpha + 1) # def abs_scaling(r): # # Fulfills (1). # return 2 / numpy.pi * numpy.arctan(r) angle = numpy.arctan2(z.imag, z.real) absval_scaled = abs_scaling(numpy.abs(z)) # We may have NaNs, so don't be too strict here. # assert numpy.all(absval_scaled >= 0) # assert numpy.all(absval_scaled <= 1) # It'd be lovely if one could claim that the grayscale of the cplot represents # exactly the absolute value of the complex number. The grayscale is computed as the # Y component of the XYZ-representation of the color, for linear SRGB values as # # 0.2126 * r + 0.7152 * g + 0.722 * b. # # Unfortunately, there is no perceptually uniform color space yet that uses # Y-luminance. CIELAB, CIECAM02, and CAM16 have their own values. if colorspace.upper() == "CAM16": L_A = 64 / numpy.pi / 5 cam = colorio.CAM16UCS(0.69, 20, L_A) srgb = colorio.SrgbLinear() # The max radius is about 21.7, but crank up colors a little bit to make the # images more saturated. This leads to SRGB-cut-off of course. # r0 = find_max_srgb_radius(cam, srgb, L=50) # r0 = 21.65824845433235 r0 = 25.0 # Rotate the angles such a "green" color represents positive real values. The # rotation is chosen such that the ratio g/(r+b) (in rgb) is the largest for the # point 1.0. offset = 0.916_708 * numpy.pi # Map (r, angle) to a point in the color space; bicone mapping similar to what # HSL looks like <https://en.wikipedia.org/wiki/HSL_and_HSV>. rd = r0 - r0 * 2 * abs(absval_scaled - 0.5) cam_pts = numpy.array([ 100 * absval_scaled, rd * numpy.cos(angle + offset), rd * numpy.sin(angle + offset), ]) # now just translate to srgb srgb_vals = srgb.to_srgb1(srgb.from_xyz100(cam.to_xyz100(cam_pts))) # Cut off the outliers. This restriction makes the representation less perfect, # but that's what it is with the SRGB color space. srgb_vals[srgb_vals > 1] = 1.0 srgb_vals[srgb_vals < 0] = 0.0 elif colorspace.upper() == "CIELAB": cielab = colorio.CIELAB() srgb = colorio.SrgbLinear() # The max radius is about 29.5, but crank up colors a little bit to make the # images more saturated. This leads to SRGB-cut-off of course. # r0 = find_max_srgb_radius(cielab, srgb, L=50) # r0 = 29.488203674554825 r0 = 45.0 # Rotate the angles such a "green" color represents positive real values. The # rotation is chosen such that the ratio g/(r+b) (in rgb) is the largest for the # point 1.0. offset = 0.893_686_8 * numpy.pi # Map (r, angle) to a point in the color space; bicone mapping similar to what # HSL looks like <https://en.wikipedia.org/wiki/HSL_and_HSV>. rd = r0 - r0 * 2 * abs(absval_scaled - 0.5) lab_pts = numpy.array([ 100 * absval_scaled, rd * numpy.cos(angle + offset), rd * numpy.sin(angle + offset), ]) # now just translate to srgb srgb_vals = srgb.to_srgb1(srgb.from_xyz100(cielab.to_xyz100(lab_pts))) # Cut off the outliers. This restriction makes the representation less perfect, # but that's what it is with the SRGB color space. srgb_vals[srgb_vals > 1] = 1.0 srgb_vals[srgb_vals < 0] = 0.0 else: assert ( colorspace.upper() == "HSL" ), f"Illegal colorspace {colorspace}. Pick one of CAM16, CIELAB, HSL." hsl = colorio.HSL() # rotate by 120 degrees to have green (0, 1, 0) for real positive numbers offset = 120 hsl_vals = numpy.array([ numpy.mod(angle / (2 * numpy.pi) * 360 + offset, 360), numpy.ones(angle.shape), absval_scaled, ]) srgb_vals = hsl.to_srgb1(hsl_vals) # iron out the -1.82131e-17 round-offs srgb_vals[srgb_vals < 0] = 0 return numpy.moveaxis(srgb_vals, 0, -1)
''' Save the visible gamut of CIELAB D65 2° in a vtk file. ''' import colorio illuminant = colorio.illuminants.d65() observer = colorio.observers.cie_1931_2() colorspace = colorio.CIELAB() colorspace.save_visible_gamut(observer, illuminant, "cielab_d65_2_visible.vtk")