Spaces:
Runtime error
Runtime error
| import os | |
| import numpy as np | |
| import tempfile | |
| import matplotlib.pyplot as plt | |
| from sklearn.metrics import euclidean_distances | |
| from skimage.io import imsave | |
| def rgb2hex(rgb_number): | |
| """ | |
| Args: | |
| - rgb_number (sequence of float) | |
| Returns: | |
| - hex_number (string) | |
| """ | |
| return '#%02x%02x%02x' % tuple([int(np.round(val * 255)) for val in rgb_number]) | |
| def hex2rgb(hexcolor_str): | |
| """ | |
| Args: | |
| - hexcolor_str (string): e.g. '#ffffff' or '33cc00' | |
| Returns: | |
| - rgb_color (sequence of floats): e.g. (0.2, 0.3, 0) | |
| """ | |
| color = hexcolor_str.strip('#') | |
| rgb = lambda x: round(int(x, 16) / 255., 5) | |
| return (rgb(color[:2]), rgb(color[2:4]), rgb(color[4:6])) | |
| def color_hist_to_palette_image(color_hist, palette, percentile=90, | |
| width=200, height=50, filename=None): | |
| """ | |
| Output the main colors in the histogram to a "palette image." | |
| Parameters | |
| ---------- | |
| color_hist : (K,) ndarray | |
| palette : rayleigh.Palette | |
| percentile : int, optional: | |
| Output only colors above this percentile of prevalence in the histogram. | |
| filename : string, optional: | |
| If given, save the resulting image to file. | |
| Returns | |
| ------- | |
| rgb_image : ndarray | |
| """ | |
| ind = np.argsort(-color_hist) | |
| ind = ind[color_hist[ind] > np.percentile(color_hist, percentile)] | |
| hex_list = np.take(palette.hex_list, ind) | |
| values = color_hist[ind] | |
| rgb_image = palette_query_to_rgb_image(dict(zip(hex_list, values))) | |
| if filename: | |
| imsave(filename, rgb_image) | |
| return rgb_image | |
| def palette_query_to_rgb_image(palette_query, width=200, height=50): | |
| """ | |
| Convert a list of hex colors and their values to an RGB image of given | |
| width and height. | |
| Args: | |
| - palette_query (dict): | |
| a dictionary of hex colors to unnormalized values, | |
| e.g. {'#ffffff': 20, '#33cc00': 0.4}. | |
| """ | |
| hex_list, values = zip(*palette_query.items()) | |
| values = np.array(values) | |
| values /= values.sum() | |
| nums = np.array(values * width, dtype=int) | |
| rgb_arrays = (np.tile(np.array(hex2rgb(x)), (num, 1)) | |
| for x, num in zip(hex_list, nums)) | |
| rgb_array = np.vstack(list(rgb_arrays)) | |
| rgb_image = rgb_array[np.newaxis, :, :] | |
| rgb_image = np.tile(rgb_image, (height, 1, 1)) | |
| return rgb_image | |
| def plot_histogram(color_hist, palette, plot_filename=None): | |
| """ | |
| Return Figure containing the color palette histogram. | |
| Args: | |
| - color_hist (K, ndarray) | |
| - palette (Palette) | |
| - plot_filename (string) [default=None]: | |
| Save histogram to this file, if given. | |
| Returns: | |
| - fig (Figure) | |
| """ | |
| fig = plt.figure(figsize=(5, 3), dpi=150) | |
| ax = fig.add_subplot(111) | |
| ax.bar( | |
| range(len(color_hist)), color_hist, | |
| color=palette.hex_list, edgecolor='black') | |
| ax.set_ylim((0, 0.3)) | |
| ax.xaxis.set_ticks([]) | |
| ax.set_xlim((0, len(palette.hex_list))) | |
| if plot_filename: | |
| fig.savefig(plot_filename, dpi=150, facecolor='none') | |
| return fig | |
| def output_histogram_base64(color_hist, palette): | |
| """ | |
| Return base64-encoded image containing the color palette histogram. | |
| Args: | |
| - color_hist (K, ndarray) | |
| - palette (Palette) | |
| Returns: | |
| - data_uri (base64 encoded string) | |
| """ | |
| _, tfname = tempfile.mkstemp('.png') | |
| plot_histogram(color_hist, palette, tfname) | |
| data_uri = open(tfname, 'rb').read().encode('base64').replace('\n', '') | |
| os.remove(tfname) | |
| return data_uri | |
| def histogram_colors_strict(lab_array, palette, plot_filename=None): | |
| """ | |
| Return a palette histogram of colors in the image. | |
| Parameters | |
| ---------- | |
| lab_array : (N,3) ndarray | |
| The L*a*b color of each of N pixels. | |
| palette : rayleigh.Palette | |
| Containing K colors. | |
| plot_filename : string, optional | |
| If given, save histogram to this filename. | |
| Returns | |
| ------- | |
| color_hist : (K,) ndarray | |
| """ | |
| # This is the fastest way that I've found. | |
| # >>> %%timeit -n 200 from sklearn.metrics import euclidean_distances | |
| # >>> euclidean_distances(palette, lab_array, squared=True) | |
| dist = euclidean_distances(palette.lab_array, lab_array, squared=True).T | |
| min_ind = np.argmin(dist, axis=1) | |
| num_colors = palette.lab_array.shape[0] | |
| num_pixels = lab_array.shape[0] | |
| color_hist = 1. * np.bincount(min_ind, minlength=num_colors) / num_pixels | |
| if plot_filename is not None: | |
| plot_histogram(color_hist, palette, plot_filename) | |
| return color_hist | |
| def histogram_colors_smoothed(lab_array, palette, sigma=10, | |
| plot_filename=None, direct=True): | |
| """ | |
| Returns a palette histogram of colors in the image, smoothed with | |
| a Gaussian. Can smooth directly per-pixel, or after computing a strict | |
| histogram. | |
| Parameters | |
| ---------- | |
| lab_array : (N,3) ndarray | |
| The L*a*b color of each of N pixels. | |
| palette : rayleigh.Palette | |
| Containing K colors. | |
| sigma : float | |
| Variance of the smoothing Gaussian. | |
| direct : bool, optional | |
| If True, constructs a smoothed histogram directly from pixels. | |
| If False, constructs a nearest-color histogram and then smoothes it. | |
| Returns | |
| ------- | |
| color_hist : (K,) ndarray | |
| """ | |
| if direct: | |
| color_hist_smooth = histogram_colors_with_smoothing( | |
| lab_array, palette, sigma) | |
| else: | |
| color_hist_strict = histogram_colors_strict(lab_array, palette) | |
| color_hist_smooth = smooth_histogram(color_hist_strict, palette, sigma) | |
| if plot_filename is not None: | |
| plot_histogram(color_hist_smooth, palette, plot_filename) | |
| return color_hist_smooth | |
| def smooth_histogram(color_hist, palette, sigma=10): | |
| """ | |
| Smooth the given palette histogram with a Gaussian of variance sigma. | |
| Parameters | |
| ---------- | |
| color_hist : (K,) ndarray | |
| palette : rayleigh.Palette | |
| containing K colors. | |
| Returns | |
| ------- | |
| color_hist_smooth : (K,) ndarray | |
| """ | |
| n = 2. * sigma ** 2 | |
| weights = np.exp(-palette.distances / n) | |
| norm_weights = weights / weights.sum(1)[:, np.newaxis] | |
| color_hist_smooth = (norm_weights * color_hist).sum(1) | |
| color_hist_smooth[color_hist_smooth < 1e-5] = 0 | |
| return color_hist_smooth | |
| def histogram_colors_with_smoothing(lab_array, palette, sigma=10): | |
| """ | |
| Assign colors in the image to nearby colors in the palette, weighted by | |
| distance in Lab color space. | |
| Parameters | |
| ---------- | |
| lab_array (N,3) ndarray: | |
| N is the number of data points, columns are L, a, b values. | |
| palette : rayleigh.Palette | |
| containing K colors. | |
| sigma : float | |
| (0,1] value to control the steepness of exponential falloff. | |
| To see the effect: | |
| >>> from pylab import * | |
| >>> ds = linspace(0,5000) # squared distance | |
| >>> sigma=10; plot(ds, exp(-ds/(2*sigma**2)), label='$\sigma=%.1f$'%sigma) | |
| >>> sigma=20; plot(ds, exp(-ds/(2*sigma**2)), label='$\sigma=%.1f$'%sigma) | |
| >>> sigma=40; plot(ds, exp(-ds/(2*sigma**2)), label='$\sigma=%.1f$'%sigma) | |
| >>> ylim([0,1]); legend(); | |
| >>> xlabel('Squared distance'); ylabel('Weight'); | |
| >>> title('Exponential smoothing') | |
| >>> #plt.savefig('exponential_smoothing.png', dpi=300) | |
| sigma=20 seems reasonable: hits 0 around squared distance of 4000. | |
| Returns: | |
| color_hist : (K,) ndarray | |
| the normalized, smooth histogram of colors. | |
| """ | |
| dist = euclidean_distances(palette.lab_array, lab_array, squared=True).T | |
| n = 2. * sigma ** 2 | |
| weights = np.exp(-dist / n) | |
| # normalize by sum: if a color is equally well represented by several colors | |
| # it should not contribute much to the overall histogram | |
| normalizing = weights.sum(1) | |
| normalizing[normalizing == 0] = 1e16 | |
| normalized_weights = weights / normalizing[:, np.newaxis] | |
| color_hist = normalized_weights.sum(0) | |
| color_hist /= lab_array.shape[0] | |
| color_hist[color_hist < 1e-5] = 0 | |
| return color_hist | |
| def makedirs(dirname): | |
| "Does what mkdir -p does, and returns dirname." | |
| if not os.path.exists(dirname): | |
| try: | |
| os.makedirs(dirname) | |
| except: | |
| print("Exception on os.makedirs") | |
| return dirname | |