alleswatikvoel/colour.py

153 lines
6.2 KiB
Python

import struct
import scipy
import scipy.misc
import scipy.cluster
import codecs
# from IPython.display import Markdown, display, HTML
import colorsys
import math
import numpy as np
import sklearn.cluster
from PIL import Image
NUM_CLUSTERS = 64
def getColourAsHex(colour):
return '#' + ''.join(format(c, '02x') for c in colour.astype(int))
def getColoursForImageByKMeansClusters(image):
"""
Adapted on answers by
Peter Hansen (http://stackoverflow.com/a/3244061)
& Johan Mickos (http://stackoverflow.com/a/34140327)
"""
im = image.copy().resize((150, 150)) # optional, to reduce time
ar = scipy.misc.fromimage(im)
shape = ar.shape
ar = ar.reshape(scipy.product(shape[:2]), shape[2])
# print( 'finding clusters')
codes, dist = scipy.cluster.vq.kmeans(ar.astype(float), NUM_CLUSTERS)
# print ('cluster centres:\n', codes)
vecs, dist = scipy.cluster.vq.vq(ar, codes) # assign codes
counts, bins = scipy.histogram(vecs, len(codes)) # count occurrences
# When only looking for single color:
# index_max = scipy.argmax(counts) # find most frequent
# peak = codes[index_max]
# colour = ''.join(chr(c) for c in peak).encode('hex')
# print( 'most frequent is %s (#%s)' % (peak, colour))
percentages = 100 * counts / sum(counts)
# print("Percentages", percentages)
# colours = [ in codes]
# print(colours)
return list(zip(codes, percentages))
def getColoursForImageByMeanShiftClusters(image, saveImgName = False):
"""
Adapted on answers by
Peter Hansen (http://stackoverflow.com/a/3244061)
& Johan Mickos (http://stackoverflow.com/a/34140327)
Now using MeanShift clustering
if saveImgName set to the path/prefix_ for the layer images, save images of each cluster matched pixels
"""
imgSize = (170,170)
# imgSize = (int(targetWidth), int(image.size[1] / (image.size[0]/targetWidth)))
im = image.copy() # optional, to reduce time
im.thumbnail(imgSize)
imgSize = im.size
print("\tRender image of", image.size,"for", imgSize)
imgAr = scipy.misc.fromimage(im)
shape = imgAr.shape
ar = imgAr.reshape(scipy.product(shape[:2]), shape[2])
total = len(ar)
# print( 'finding clusters')
# codes, dist = scipy.cluster.vq.kmeans(ar.astype(float), NUM_CLUSTERS)
bandwidth = sklearn.cluster.estimate_bandwidth(ar.astype(float), quantile=0.1, n_samples=500)
ms = sklearn.cluster.MeanShift(bandwidth=bandwidth)
ms.fit(ar.astype(float))
labels = ms.labels_ # labels per point
cluster_centers = ms.cluster_centers_ # centers of found clusters
labels_unique = np.unique(labels)
n_clusters_ = len(labels_unique)
print("\tClusters found:", n_clusters_)
colours = []
for k in labels_unique:
cluster_center = cluster_centers[k] # np.array with rgb
mask = labels == k # True/False map of flattened array
# m = np.ma.masked_where(labels != k, labels)
count = len(ar[mask]) # nr of pixels for this label
# pass on percentages:
colours.append((cluster_center, 100 * count / total))
if saveImgName:
# save image as RGBA with transparent pixels, except for there where label == k
layer = np.array([np.append(ar[i],255) if l == k else [0,0,0,0] for i,l in enumerate(list(labels))], dtype=np.uint8)
layerImgAr = layer.reshape((imgSize[1],imgSize[0],4)) # layers are stacked: vertical, horizontal, rgba
layerImg = Image.fromarray(layerImgAr, mode="RGBA")
layerFilename = saveImgName + '-%s.png' % k
layerImg.save(layerFilename)
print(layerFilename)
# display(HTML("<span style='background:%s'>%s</span>" % (getColourAsHex(cluster_center),getColourAsHex(cluster_center))))
return colours
def getColoursForImageByPxAvg(image):
im = image.copy().resize((8, 8))
pixels = np.concatenate(scipy.misc.fromimage(im))
# colours = ['#' + ''.join(format(c, '02x') for c in color.astype(int)) for color in pixels]
percentages = np.zeros(len(pixels)) + (100 / len(pixels))
return list(zip(pixels, percentages))
def getColoursAsHTML(colours):
return " ".join(['<span style="background:%s">%s - (%s %%)</span>' % (getColourAsHex(colour[0]), getColourAsHex(colour[0]), colour[1]) for colour in colours]);
def loadColoursFromDbImages(images):
return [i.colours for i in images if not i.colours is None]
def getSvgFromDbImages(images, elId = ""):
# sum concatenates all colour arrays
# allColours = []
# for c in loadColoursFromDbImages(images):
# allColours += c
# box 160, because center or circle = 100 => +/- 50 => + r of colour circle (max: 10) => 160
# svg = '<svg viewBox="-160 -160 320 320" xmlns="http://www.w3.org/2000/svg" id="%s">' % (elId, )
svg = '<svg viewBox="-160 -160 320 320" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:95%" id="colourImage">' # because of IE this is fully defined here
radius = 100
for image in images:
if image.colours is None:
continue
for i, colour in enumerate(image.colours):
colourId = '%s-%s' % (image.id, i)
rgb, percentage = colour
rgbNorm = rgb/255
hsv = colorsys.rgb_to_hsv(rgbNorm[0], rgbNorm[1], rgbNorm[2])
# find position on circle
radians = 2 * math.pi * hsv[0]
x = math.cos(radians)
y = math.sin(radians)
# based on saturation, we move inwards/outwards
# min = 0.5, max = 1.5 (dus + 0.5)
pos = np.array([x,y]) * (0.5 + hsv[1]) * radius
# Posibilitiy: determine position based on avg(saturation, value) => dark & grey inside, shiney and colourful outside
# pos = np.array([x,y]) * (0.5 + (hsv[1]+hsv[2])/2) * radius
r = max(1,-10/percentage+10) # as r, we converge to maximum radius 10, but don't want to get smaller radi then 1
c = '<circle cx="%s" cy="%s" r="%s" style="fill:%s" onmouseover="triggerover(\'%s\', this)"></circle>' % (pos[0], pos[1], r, getColourAsHex(rgb), colourId)
svg += c
svg += "</svg>"
return svg