nowsyn's picture
upload codes
54a7220
raw
history blame
8.33 kB
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