PKX\NUr[[dominant_color/__init__.py"""Just a demo of different dominant color extraction algorithms""" __version__ = "0.0.1" PK\NxMMdominant_color/image_palette.pyimport argparse from matplotlib import pyplot as plt import matplotlib.image as mpimg from dominant_color.kmeans_palette import kmeans_palette from dominant_color.mediancut_palette import mediancut_palette def parse_args(): parser = argparse.ArgumentParser( description="Test different color extraction algorithms." ) parser.add_argument("image") subparsers = parser.add_subparsers(help="Algorithms") kmeans = subparsers.add_parser("kmeans", help="Using K-Means clustering") kmeans.add_argument( "--bins", help="Work on this given number of color bins", type=int, default=5 ) kmeans.add_argument( "--workimage-width", help="Work on an reduced image of the given width", default=150, ) kmeans.add_argument( "--workimage-height", help="Work on an reduced image of the given height", default=150, ) kmeans.set_defaults(algo=kmeans_palette) mediancut = subparsers.add_parser( "mediancut", help="Using a median cut quantization" ) mediancut.add_argument( "--bins", help="Work on this given number of color bins", type=int, default=5 ) mediancut.add_argument( "--quality", help="Only use 1/quality pixels. (1 means use every pixels)", type=int, default=10, ) mediancut.set_defaults(algo=mediancut_palette) args = parser.parse_args() if not hasattr(args, 'algo'): parser.print_help() exit(1) return args def display_palette(image_path, palette): fig = plt.figure() ax = fig.add_subplot(2, 1, 1) ax.imshow(mpimg.imread(image_path)) ax.set_yticklabels([]) ax.set_xticklabels([]) ax = fig.add_subplot(2, 1, 2) ax.bar( range(len(palette)), [pct for pct, color in palette], color=[(r / 255, g / 255, b / 255) for pct, (r, g, b) in palette], ) ax.set_ylabel("percent") ax.set_xlabel("color") plt.show() def main(): args = vars(parse_args()) display_palette(args["image"], args.pop("algo")(**args)) if __name__ == "__main__": main() PK[N] dominant_color/kmeans_palette.pyfrom PIL import Image import numpy as np import scipy import scipy.misc import scipy.cluster def kmeans_palette(image, bins=5, workimage_width=150, workimage_height=150): im = Image.open(image) im = im.resize((workimage_width, workimage_height)) ar = np.asarray(im) shape = ar.shape # Flatten as a list of pixels ar = ar.reshape(scipy.product(shape[:2]), shape[2]).astype(float) # Finding clusters codebook, _ = scipy.cluster.vq.kmeans(ar, bins) # codebook contain 5 middle of bins, as [r, g, b] code, _ = scipy.cluster.vq.vq(ar, codebook) # code contains bin ids of each pixel counts, _ = scipy.histogram(code, bins) # Binning cluster ids to count them palette = sorted( [ (100 * counts[i] / (workimage_width * workimage_height), codebook[i]) for i in range(bins) ], reverse=True, ) return palette PK%[NG$))#dominant_color/mediancut_palette.py# -*- coding: utf-8 -*- """ colorthief ~~~~~~~~~~ Grabbing the color palette from an image. :copyright: (c) 2015 by Shipeng Feng. :license: BSD, see LICENSE for more details. """ __version__ = "0.2.1" # from collections import PriorityQueue from PIL import Image def mediancut_palette(image, bins=5, quality=10): image = Image.open(image).convert("RGBA") width, height = image.size pixels = image.getdata() pixel_count = width * height valid_pixels = [] for i in range(0, pixel_count, quality): r, g, b, a = pixels[i] # If pixel is mostly opaque and not white if a >= 125: if not (r > 250 and g > 250 and b > 250): valid_pixels.append((r, g, b)) # Send array to quantize function which clusters values # using median cut algorithm palette = MMCQ.quantize(valid_pixels, bins) total_volume = sum(volume for color, volume in palette) return [(100 * volume / total_volume, color) for color, volume in palette] class MMCQ(object): """Basic Python port of the MMCQ (modified median cut quantization) algorithm from the Leptonica library (http://www.leptonica.com/). """ SIGBITS = 5 RSHIFT = 8 - SIGBITS MAX_ITERATION = 1000 FRACT_BY_POPULATIONS = 0.75 @staticmethod def get_color_index(r, g, b): return (r << (2 * MMCQ.SIGBITS)) + (g << MMCQ.SIGBITS) + b @staticmethod def get_histo(pixels): """histo (1-d array, giving the number of pixels in each quantized region of color space) """ histo = dict() for pixel in pixels: rval = pixel[0] >> MMCQ.RSHIFT gval = pixel[1] >> MMCQ.RSHIFT bval = pixel[2] >> MMCQ.RSHIFT index = MMCQ.get_color_index(rval, gval, bval) histo[index] = histo.setdefault(index, 0) + 1 return histo @staticmethod def vbox_from_pixels(pixels, histo): rmin = 1000000 rmax = 0 gmin = 1000000 gmax = 0 bmin = 1000000 bmax = 0 for pixel in pixels: rval = pixel[0] >> MMCQ.RSHIFT gval = pixel[1] >> MMCQ.RSHIFT bval = pixel[2] >> MMCQ.RSHIFT rmin = min(rval, rmin) rmax = max(rval, rmax) gmin = min(gval, gmin) gmax = max(gval, gmax) bmin = min(bval, bmin) bmax = max(bval, bmax) return VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo) @staticmethod def median_cut_apply(histo, vbox): if not vbox.count: return (None, None) rw = vbox.r2 - vbox.r1 + 1 gw = vbox.g2 - vbox.g1 + 1 bw = vbox.b2 - vbox.b1 + 1 maxw = max([rw, gw, bw]) # only one pixel, no split if vbox.count == 1: return (vbox.copy, None) # Find the partial sum arrays along the selected axis. total = 0 sum_ = 0 partialsum = {} lookaheadsum = {} do_cut_color = None if maxw == rw: do_cut_color = "r" for i in range(vbox.r1, vbox.r2 + 1): sum_ = 0 for j in range(vbox.g1, vbox.g2 + 1): for k in range(vbox.b1, vbox.b2 + 1): index = MMCQ.get_color_index(i, j, k) sum_ += histo.get(index, 0) total += sum_ partialsum[i] = total elif maxw == gw: do_cut_color = "g" for i in range(vbox.g1, vbox.g2 + 1): sum_ = 0 for j in range(vbox.r1, vbox.r2 + 1): for k in range(vbox.b1, vbox.b2 + 1): index = MMCQ.get_color_index(j, i, k) sum_ += histo.get(index, 0) total += sum_ partialsum[i] = total else: # maxw == bw do_cut_color = "b" for i in range(vbox.b1, vbox.b2 + 1): sum_ = 0 for j in range(vbox.r1, vbox.r2 + 1): for k in range(vbox.g1, vbox.g2 + 1): index = MMCQ.get_color_index(j, k, i) sum_ += histo.get(index, 0) total += sum_ partialsum[i] = total for i, d in partialsum.items(): lookaheadsum[i] = total - d # determine the cut planes dim1 = do_cut_color + "1" dim2 = do_cut_color + "2" dim1_val = getattr(vbox, dim1) dim2_val = getattr(vbox, dim2) for i in range(dim1_val, dim2_val + 1): if partialsum[i] > (total / 2): vbox1 = vbox.copy vbox2 = vbox.copy left = i - dim1_val right = dim2_val - i if left <= right: d2 = min([dim2_val - 1, int(i + right / 2)]) else: d2 = max([dim1_val, int(i - 1 - left / 2)]) # avoid 0-count boxes while not partialsum.get(d2, False): d2 += 1 count2 = lookaheadsum.get(d2) while not count2 and partialsum.get(d2 - 1, False): d2 -= 1 count2 = lookaheadsum.get(d2) # set dimensions setattr(vbox1, dim2, d2) setattr(vbox2, dim1, getattr(vbox1, dim2) + 1) return (vbox1, vbox2) return (None, None) @staticmethod def quantize(pixels, max_color): """Quantize. :param pixels: a list of pixel in the form (r, g, b) :param max_color: max number of colors """ if not pixels: raise Exception("Empty pixels when quantize.") if max_color < 2 or max_color > 256: raise Exception("Wrong number of max colors when quantize.") histo = MMCQ.get_histo(pixels) # check that we aren't below maxcolors already if len(histo) <= max_color: # generate the new colors from the histo and return pass # get the beginning vbox from the colors vbox = MMCQ.vbox_from_pixels(pixels, histo) pq = PQueue(lambda x: x.count) pq.push(vbox) # inner function to do the iteration def iter_(lh, target): n_color = 1 n_iter = 0 while n_iter < MMCQ.MAX_ITERATION: vbox = lh.pop() if not vbox.count: # just put it back lh.push(vbox) n_iter += 1 continue # do the cut vbox1, vbox2 = MMCQ.median_cut_apply(histo, vbox) if not vbox1: raise Exception("vbox1 not defined; shouldn't happen!") lh.push(vbox1) if vbox2: # vbox2 can be null lh.push(vbox2) n_color += 1 if n_color >= target: return if n_iter > MMCQ.MAX_ITERATION: return n_iter += 1 # first set of colors, sorted by population iter_(pq, MMCQ.FRACT_BY_POPULATIONS * max_color) # Re-sort by the product of pixel occupancy times the size in # color space. pq2 = PQueue(lambda x: x.count * x.volume) while pq.size(): pq2.push(pq.pop()) # next set - generate the median cuts using the (npix * vol) sorting. iter_(pq2, max_color - pq2.size()) pq2.sort() return list( reversed([(vbox.avg, vbox.count * vbox.volume) for vbox in pq2.contents]) ) class VBox(object): """3d color space box""" def __init__(self, r1, r2, g1, g2, b1, b2, histo): self.r1 = r1 self.r2 = r2 self.g1 = g1 self.g2 = g2 self.b1 = b1 self.b2 = b2 self.histo = histo @property def volume(self): sub_r = self.r2 - self.r1 sub_g = self.g2 - self.g1 sub_b = self.b2 - self.b1 return (sub_r + 1) * (sub_g + 1) * (sub_b + 1) @property def copy(self): return VBox(self.r1, self.r2, self.g1, self.g2, self.b1, self.b2, self.histo) @property def avg(self): ntot = 0 mult = 1 << (8 - MMCQ.SIGBITS) r_sum = 0 g_sum = 0 b_sum = 0 for i in range(self.r1, self.r2 + 1): for j in range(self.g1, self.g2 + 1): for k in range(self.b1, self.b2 + 1): histoindex = MMCQ.get_color_index(i, j, k) hval = self.histo.get(histoindex, 0) ntot += hval r_sum += hval * (i + 0.5) * mult g_sum += hval * (j + 0.5) * mult b_sum += hval * (k + 0.5) * mult if ntot: r_avg = int(r_sum / ntot) g_avg = int(g_sum / ntot) b_avg = int(b_sum / ntot) else: r_avg = int(mult * (self.r1 + self.r2 + 1) / 2) g_avg = int(mult * (self.g1 + self.g2 + 1) / 2) b_avg = int(mult * (self.b1 + self.b2 + 1) / 2) return r_avg, g_avg, b_avg def contains(self, pixel): rval = pixel[0] >> MMCQ.RSHIFT gval = pixel[1] >> MMCQ.RSHIFT bval = pixel[2] >> MMCQ.RSHIFT return all( [ rval >= self.r1, rval <= self.r2, gval >= self.g1, gval <= self.g2, bval >= self.b1, bval <= self.b2, ] ) @property def count(self): npix = 0 for i in range(self.r1, self.r2 + 1): for j in range(self.g1, self.g2 + 1): for k in range(self.b1, self.b2 + 1): index = MMCQ.get_color_index(i, j, k) npix += self.histo.get(index, 0) return npix class PQueue: """Simple priority queue.""" def __init__(self, sort_key): self.sort_key = sort_key self.contents = [] self._sorted = False def sort(self): self.contents.sort(key=self.sort_key) self._sorted = True def push(self, o): self.contents.append(o) self._sorted = False def peek(self, index=None): if not self._sorted: self.sort() if index is None: index = len(self.contents) - 1 return self.contents[index] def pop(self): if not self._sorted: self.sort() return self.contents.pop() def size(self): return len(self.contents) PK!H ʀ<D/dominant_color-0.0.1.dist-info/entry_points.txtN+I/N.,()JK+M/q\ĜԒT<..PK!HPO$dominant_color-0.0.1.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UrPK!H4'dominant_color-0.0.1.dist-info/METADATAQMo1Wpp r a8-`|6MgYCϤj!@%t,)>}ϒ!?u*l6O/jm@C9S$ ?'IAUUBOqL>邢m_2Zd&0; &u*{)Njp{:(s#Ω;ȝN0R)n@PK!H(%dominant_color-0.0.1.dist-info/RECORDr0@}(.@ EE~&$ ߻q޶3wN6ì=BL0P+T;OUaI O@w}ĵa쭷3aIOvqQQ>³ѕ]Zع ϪDLuR~Xw