← back to Dw Discovery
enrich_idesign.py
47 lines
import json,urllib.request,io
from collections import Counter
from concurrent.futures import ThreadPoolExecutor
from PIL import Image
LEX={"Chalk":"#f3f0e9","Alabaster":"#eee9df","Ivory":"#efe7d3","Cream":"#efe2c6","Linen":"#e9e0cf","Bone":"#e3d9c6","Porcelain":"#ece9e2","Oatmeal":"#d9cdb8","Flax":"#ddceac","Greige":"#cabfae","Mushroom":"#b9ad9c","Putty":"#c8bca9","Stone":"#b8b0a3","Sand":"#d8c7a6","Taupe":"#b6a892","Pebble":"#c2bbb0","Tan":"#c9a875","Camel":"#bf9b6a","Khaki":"#b3a06e","Caramel":"#a9743f","Chestnut":"#6e4326","Tobacco":"#7a5a36","Walnut":"#5a4632","Mocha":"#6f5847","Espresso":"#3f2f25","Dove":"#cfccc6","Silver":"#c8c8c8","Ash":"#b2b2ad","Fog":"#c4c8c9","Smoke":"#9b9d9c","Pewter":"#8e9192","Slate":"#5d676d","Graphite":"#454a4d","Charcoal":"#3a3a3a","Ink":"#1f2730","Noir":"#171717","Onyx":"#222124","Ebony":"#2a2320","Jet":"#101012","Celadon":"#9bb6a1","Pistachio":"#b6c79a","Sage":"#9caa8a","Eucalyptus":"#8fa595","Moss":"#6f7d4e","Olive":"#7a7a45","Loden":"#4f5d3a","Hunter":"#33523f","Forest":"#2f4a36","Emerald":"#2f7a55","Verdigris":"#5e9a8a","Powder":"#cfe0ea","Sky":"#a9cbe0","Wedgwood":"#7fa0c0","Cornflower":"#7a93d0","French Blue":"#5b7fb0","Cerulean":"#3f7fb0","Denim":"#4b6786","Teal":"#357a7d","Peacock":"#1f6f78","Prussian":"#28415c","Indigo":"#33406b","Navy":"#27314f","Midnight":"#1b2436","Buttercream":"#f3e7b8","Champagne":"#e6d6a8","Maize":"#e6cf6b","Citrine":"#d8c047","Saffron":"#d99e2b","Amber":"#c9962f","Ochre":"#b5852f","Mustard":"#c9a227","Gold":"#c4a35a","Brass":"#b08d57","Apricot":"#e7b48a","Peach":"#e9c1a3","Clay":"#b97a55","Terracotta":"#b5683f","Persimmon":"#c75c39","Pumpkin":"#c4702a","Copper":"#a5683f","Rust":"#9a5230","Sienna":"#8a4b32","Blush":"#e7c4c2","Rose":"#d99fa6","Dusty Rose":"#c08f90","Coral":"#e3795f","Cinnabar":"#b8432f","Brick":"#9a4435","Oxblood":"#5e2526","Claret":"#6e2233","Garnet":"#7a2030","Burgundy":"#6e2233","Crimson":"#a01f2e","Lavender":"#cdc1de","Lilac":"#c7b1d6","Heather":"#a892ad","Mauve":"#a98792","Amethyst":"#8a6aa0","Plum":"#6a4768","Aubergine":"#4a2f44","Platinum":"#d6d3cb","Bronze":"#8a6a40","Gunmetal":"#4d5356"}
FAM={'Chalk':'White','Alabaster':'White','Ivory':'White','Cream':'Cream','Linen':'Cream','Bone':'White','Porcelain':'White','Oatmeal':'Beige','Flax':'Beige','Greige':'Neutral','Mushroom':'Neutral','Putty':'Neutral','Stone':'Neutral','Sand':'Beige','Taupe':'Brown','Pebble':'Neutral','Tan':'Brown','Camel':'Brown','Khaki':'Brown','Caramel':'Brown','Chestnut':'Brown','Tobacco':'Brown','Walnut':'Brown','Mocha':'Brown','Espresso':'Brown','Dove':'Gray','Silver':'Gray','Ash':'Gray','Fog':'Gray','Smoke':'Gray','Pewter':'Gray','Slate':'Gray','Graphite':'Gray','Charcoal':'Gray','Ink':'Black','Noir':'Black','Onyx':'Black','Ebony':'Black','Jet':'Black','Celadon':'Green','Pistachio':'Green','Sage':'Green','Eucalyptus':'Green','Moss':'Green','Olive':'Green','Loden':'Green','Hunter':'Green','Forest':'Green','Emerald':'Green','Verdigris':'Green','Powder':'Blue','Sky':'Blue','Wedgwood':'Blue','Cornflower':'Blue','French Blue':'Blue','Cerulean':'Blue','Denim':'Blue','Teal':'Aqua','Peacock':'Aqua','Prussian':'Blue','Indigo':'Blue','Navy':'Blue','Midnight':'Blue','Buttercream':'Yellow','Champagne':'Gold','Maize':'Yellow','Citrine':'Yellow','Saffron':'Gold','Amber':'Gold','Ochre':'Gold','Mustard':'Yellow','Gold':'Gold','Brass':'Gold','Apricot':'Orange','Peach':'Orange','Clay':'Terracotta','Terracotta':'Terracotta','Persimmon':'Orange','Pumpkin':'Orange','Copper':'Orange','Rust':'Orange','Sienna':'Brown','Blush':'Pink','Rose':'Pink','Dusty Rose':'Pink','Coral':'Coral','Cinnabar':'Red','Brick':'Red','Oxblood':'Burgundy','Claret':'Burgundy','Garnet':'Burgundy','Burgundy':'Burgundy','Crimson':'Red','Lavender':'Purple','Lilac':'Purple','Heather':'Purple','Mauve':'Purple','Amethyst':'Purple','Plum':'Purple','Aubergine':'Purple','Platinum':'Gray','Bronze':'Gold','Gunmetal':'Gray'}
hx=lambda h:tuple(int(h.lstrip('#')[i:i+2],16) for i in (0,2,4))
LV=[(n,hx(v)) for n,v in LEX.items()]
def nearest(rgb): return min(LV,key=lambda kv:sum((a-b)**2 for a,b in zip(rgb,kv[1])))[0]
def palette(url):
raw=urllib.request.urlopen(urllib.request.Request(url,headers={'User-Agent':'Mozilla/5.0'}),timeout=20).read()
im=Image.open(io.BytesIO(raw)).convert('RGB').resize((100,100))
q=im.quantize(colors=8,method=Image.MEDIANCUT)
pal=q.getpalette(); idxs=list(q.getdata()); total=len(idxs)
cnt=Counter(idxs)
out=[]
for idx,c in cnt.most_common():
rgb=(pal[idx*3],pal[idx*3+1],pal[idx*3+2]); pct=round(100*c/total)
out.append({'hex':'#%02x%02x%02x'%rgb,'name':nearest(rgb),'bucket':FAM.get(nearest(rgb),'Neutral'),'pct':pct})
# merge same colorway-name, drop <3%, cap 6
merged={}
for c in out:
k=c['name']
if k in merged: merged[k]['pct']+=c['pct']
else: merged[k]=dict(c)
pal2=sorted([c for c in merged.values() if c['pct']>=3],key=lambda c:-c['pct'])[:6]
return pal2
def work(p):
try:
pal=palette(p['image'])
if not pal: return p['id'],None
return p['id'],{'palette':pal,'hex':pal[0]['hex'],'colorName':pal[0]['name'],'colorBucket':pal[0]['bucket']}
except Exception: return p['id'],None
prods=json.load(open('data/products.staged.json')); res={}; done=0
with ThreadPoolExecutor(max_workers=24) as ex:
for pid,r in ex.map(work,prods):
done+=1
if r:res[pid]=r
if done%500==0:print(f"{done}/{len(prods)} ({len(res)} ok)",flush=True)
for p in prods:
if p['id'] in res: p.update(res[p['id']])
json.dump(prods,open('data/products.staged.json','w'))
withpal=sum(1 for p in prods if p.get('palette'))
import statistics
avgc=statistics.mean(len(p['palette']) for p in prods if p.get('palette'))
print(f"DONE: {withpal}/{len(prods)} enriched with full palette; avg {avgc:.1f} colors/product")