def _diff_husl(a, b, max_rgb_diff=2): """Checks HUSL conversion by converting HUSL to RGB. Both HUSL triplets/arrays should produce the same RGB triplet/array.""" rgb_a = nphusl.to_rgb(a).astype(np.int) rgb_b = nphusl.to_rgb(b).astype(np.int) make_fail = lambda: "HSL diff: {} (HSL-A: {} HSL-B: {})".format( a - b, a, b) # we check that the HUSL->RGB conversion for both # a and b are within 2 (in [0, 255] scale), and if that's not # true we may be dealing with a low saturation pixel or a dark # pixel where humans can't really tell the difference, so # we check the raw HUSL values so that they're within 2.0 try: assert np.all(np.abs(rgb_a - rgb_b) <= max_rgb_diff), make_fail() except AssertionError: a = np.asarray(a) b = np.asarray(b) a_hue, a_sat, a_lit = (a[..., n] for n in range(3)) b_hue, b_sat, b_lit = (b[..., n] for n in range(3)) _diff(a_hue, b_hue, 0.3) _diff(a_sat, b_sat, 2.0) # HUSL saturation is hard to approximate _diff(a_lit, b_lit, 0.1) imageio.imwrite("horkle.jpg", _img())
def melonize(img, n_frames): hsl = nphusl.to_husl(img) hue, sat, lit = (hsl[..., n] for n in range(3)) #sat[:] = 99 pink = 360 # very end of the H spectrum green = 130 def gen_chunksizes(): yield from range(1, 100) yield from range(100, 1, -1) for chunksize in gen_chunksizes(): hsl_out = hsl.copy() hue_out, sat_out, lit_out = (hsl_out[..., i] for i in range(3)) for low, high in nphusl.chunk(100, chunksize): # chunks of the hue range select = np.logical_and(lit > low, lit < high) is_odd = low % (chunksize * 2) color = pink if is_odd else green hue_out[select] = color select = np.logical_and(lit > (low - 1), lit < low) select = np.logical_and(select, lit > 60) ave = (low + high) / 2 select = np.logical_and(lit > (ave - 2), lit < (ave + 2)) sat_out[select] = 100 yield nphusl.to_rgb(hsl_out)
def microwave(img): hsl = nphusl.to_husl(img) hue = hsl[..., 0] rows, cols = hue.shape yield nphusl.to_rgb(hsl) while True: for chunk, ((rs, re), (cs, ce)) in nphusl.chunk_img(hue, chunksize=8): hue_left = hue[rs, cs - 1] hue_up = hue[rs - 1, cs] this_hue = chunk[0, 0] new_hue = (-random.randrange(30, 50) * (hue_up / 360) - 10 * random.randrange(1, 10) * (hue_left / 360)) new_hue = (15 * this_hue + 2 * new_hue) / 17 chunk[:] = new_hue np.mod(hue, 360, out=hue) yield nphusl.to_rgb(hsl)
def test_to_rgb_2d(): img = _img()[:, 0] int_img = np.ndarray(shape=img.shape, dtype=np.uint8) int_img[:] = img * 255 husl = nphusl.rgb_to_husl(img) rgb = nphusl.to_rgb(husl) assert np.all(rgb == int_img)
def microwave(img): hsl = nphusl.to_husl(img) hue = hsl[..., 0] rows, cols = hue.shape yield nphusl.to_rgb(hsl) while True: for chunk, ((rs, re), (cs, ce)) in nphusl.chunk_img(hue, chunksize=8): hue_left = hue[rs, cs-1] hue_up = hue[rs-1, cs] this_hue = chunk[0, 0] new_hue = (-random.randrange(30, 50) * (hue_up / 360) -10*random.randrange(1, 10) * (hue_left / 360)) new_hue = (15*this_hue + 2*new_hue) / 17 chunk[:] = new_hue np.mod(hue, 360, out=hue) yield nphusl.to_rgb(hsl)
def reveal_blue(img): """Easy mode! Selecting bluish pixels with the HUSL color space.""" # convert an integer RGB image to HUSL array of floats hsl = nphusl.to_husl(img) hue = hsl[..., 0] # separate out the hue channel # create a mask for pixels with hues between 250 and 290 (blue) bluish = np.logical_and(hue > 250, hue < 290) hsl[..., 2][~bluish] *= 0.5 # halve lightness of non-bluish areas return nphusl.to_rgb(hsl), "blue"
def hue_watermelon(img): hsl = nphusl.to_husl(img) hue, saturation, lightness = (hsl[..., n] for n in range(3)) hue_out = hue.copy() pink = 360 # very end of the H spectrum green = 130 chunksize = 45 for low, high in nphusl.chunk(360, chunksize): # chunks of the hue range select = np.logical_and(hue > low, hue < high) is_odd = low % (chunksize * 2) color = pink if is_odd else green hue_out[select] = color hue[:] = hue_out return nphusl.to_rgb(hsl), "watermelon"
def highlight_saturation(img): hsl = nphusl.to_husl(img) hsl[..., 2][hsl[..., 1] < 80] = 0 return nphusl.to_rgb(hsl), "saturation"
def reveal_light(img): hsl = nphusl.to_husl(img) lightness = hsl[..., 2] # just the lightness channel dark = lightness < 62 hsl[..., 2][dark] = 0 # darkish areas to completely dark return nphusl.to_rgb(hsl), "light"
def test_to_rgb_2d(): img = np.ascontiguousarray(_img()[:, 17]) husl = nphusl.to_husl(img) rgb = nphusl.to_rgb(husl) _diff(rgb, img, diff=1)
def test_to_rgb_triplet(): assert type(nphusl.to_rgb([360, 100, 100])) == np.ndarray assert np.all(nphusl.to_rgb([360, 100, 100]) == [255, 255, 255])
def test_accuracy(img): with nphusl.simd_enabled(): hsl = nphusl.to_husl(img.rgb) with nphusl.numpy_enabled(): rgb = nphusl.to_rgb(hsl) hsl_ref = nphusl.to_husl(img.rgb) size = hsl.shape[0] * hsl.shape[1] hsl_flat = hsl.reshape((size, 3)) rgb_flat = rgb.reshape((size, 3)) hsl_ref_flat = hsl_ref.reshape((size, 3)) rgb_ref_flat = img.rgb.reshape((size, 3)) rgb_diff = np.abs(rgb_flat.astype(int) - rgb_ref_flat) hsl_diff = np.abs(hsl_flat - hsl_ref_flat) h_err, s_err, l_err = (hsl_diff[..., n] for n in range(3)) h, s, l = (hsl_flat[..., n] for n in range(3)) percentiles = [0, 25, 50, 90, 95, 96, 97, 98, 99.5, 99.6, 99.7, 99.8, 99.9, 100] def print_err_tables(): fields = "Percentile", "Red error", "Green error", "Blue error", " " print(BOLD + "\nIMG->HUSL->RGB roundtrip error" + END) print(_error_table(rgb_diff, percentiles, fields)) fields = "Percentile", "Hue error", "Sat error", "Light error", " " print(BOLD + "\nIMG->HUSL error vs. reference impl." + END) print(_error_table(hsl_diff, percentiles, fields)) for i, name in enumerate("hue saturation lightness".split()): c = hsl_flat[..., i] c_err = hsl_diff[..., i] err_99 = np.percentile(c_err, 99) print(BOLD + "\nTypical RGB for {} error above 99th " "percentile: ".format(name) + END) print(img.rgb[c_err.reshape(rgb.shape[:-1]) > err_99]) print(BOLD + "\nTypical HUSL for {} error above 99th " "percentile: ".format(name) + END) print(hsl[c_err.reshape(rgb.shape[:-1]) > err_99]) print(BOLD + "\nAll RGB & HUSL errors") print( "=====================" + END) print_err_tables() h_err[s < 0.1] = 0 # hue errors for low saturation have no meaning rgb_diff[s < 0.1] = 0 s_err[l > 99.5] = 0 # saturation errors when very bright not meaningful rgb_diff[l > 99.5] = 0 s_err[l < 1] = 0 # saturation errors when very dim not meaningful rgb_diff[l < 1] = 0 print(BOLD + "\nPerceptible RGB & HUSL errors") print( "=============================" + END) print_err_tables() max_err = max(np.percentile(rgb_diff, 100, axis=0)) mask = np.any(rgb_diff == max_err, axis=1) print(BOLD + "\nMost challenging pixel") print( "======================" + END) print("RGB input vs. output: {} -> {}".format( rgb_ref_flat[mask].squeeze(), rgb_flat[mask].squeeze())) print("HUSL ouput vs. reference impl.: {} vs. {}".format( hsl_flat[mask].squeeze(), hsl_ref_flat[mask].squeeze())) src = "_accuracy_test_source.png" rec = "_accuracy_test_recreated.png" print("\nWriting PNGs: {}, {}".format(src, rec)) imageio.imwrite(src, img.rgb) imageio.imwrite(rec, rgb)
def test_to_rgb_3d(): img = _img() husl = _nphusl.to_husl(img) rgb = nphusl.to_rgb(husl) assert np.all(rgb == img)