Spaces:
Runtime error
Runtime error
Initial Commit
Browse files- annotations/abercrombie_women.pickle +3 -0
- annotations/aritzia.pickle +3 -0
- annotations/banana_annotations.pickle +3 -0
- annotations/dynamite.pickle +3 -0
- annotations/hnm_annotations.pickle +3 -0
- annotations/street_annotations.pickle +3 -0
- annotations/uniqlo_annotations.pickle +3 -0
- app.py +452 -0
- objective.PNG +0 -0
- requirements.txt +2 -0
annotations/abercrombie_women.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:2489e4154b2d035c7c0ddee66100e642da754c03e005e292a301618abe70cf06
|
3 |
+
size 67188
|
annotations/aritzia.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a9501466b2d4327aa29a2fdfcdb2de712821323dba16cbb7c200d5107ecf5814
|
3 |
+
size 90000
|
annotations/banana_annotations.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:61b679f59f989e012530ab5c3c2bf5246507b34c9aa4844ffe9e93d552e3fb71
|
3 |
+
size 49980
|
annotations/dynamite.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:2d9babf9f122ebae53223e2ac8edb6c2666a304df614239876bab91c3b8e93fe
|
3 |
+
size 64847
|
annotations/hnm_annotations.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:e5eb93941f94524318a41537e90c2a2df1ffdf094c19cb3f9b06f3e5bf7af18b
|
3 |
+
size 444989
|
annotations/street_annotations.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:573e43757b4348138156518f25dffed3260659382be2aa5c5ff47db56b7c18a4
|
3 |
+
size 80297
|
annotations/uniqlo_annotations.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:64a78ebb310af49d972e9b579b42fac8b8042ea5c06669610a8a542435cf649d
|
3 |
+
size 38733
|
app.py
ADDED
@@ -0,0 +1,452 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import subprocess
|
2 |
+
subprocess.run(['wget', '-N', '-q', 'https://ampl.com/dl/open/ipopt/ipopt-linux64.zip'], stdout=subprocess.DEVNULL)
|
3 |
+
subprocess.run(['unzip', '-o', '-q', 'ipopt-linux64'], stdout=subprocess.DEVNULL)
|
4 |
+
|
5 |
+
from pyomo.environ import *
|
6 |
+
import pickle
|
7 |
+
|
8 |
+
import pickle
|
9 |
+
dict_files = {
|
10 |
+
'banana republic': 'annotations/banana_annotations.pickle',
|
11 |
+
'h&m': 'annotations/hnm_annotations.pickle',
|
12 |
+
'uniqlo': 'annotations/uniqlo_annotations.pickle',
|
13 |
+
'street': 'annotations/street_annotations.pickle',
|
14 |
+
'abercrombie women': 'annotations/abercrombie_women.pickle',
|
15 |
+
'aritzia': 'annotations/aritzia.pickle',
|
16 |
+
'dynamite': 'annotations/dynamite.pickle',
|
17 |
+
}
|
18 |
+
|
19 |
+
style_gender = {
|
20 |
+
'banana republic': 'male',
|
21 |
+
'h&m': 'male',
|
22 |
+
'uniqlo': 'male',
|
23 |
+
'street': 'male',
|
24 |
+
'abercrombie women': 'female',
|
25 |
+
'aritzia': 'female',
|
26 |
+
'dynamite': 'female',
|
27 |
+
}
|
28 |
+
|
29 |
+
gender_article_keys = {
|
30 |
+
'male': ['shirt', 'pants_or_shorts', 'hoodie_or_jacket', 'hat', 'shoes'],
|
31 |
+
'female': ['top', 'bottom', 'dress', 'hoodie_or_jacket', 'hat', 'shoes']
|
32 |
+
}
|
33 |
+
|
34 |
+
style_annotations_dict = {}
|
35 |
+
|
36 |
+
def get_annotations(dict_file):
|
37 |
+
with open(dict_file, 'rb') as f:
|
38 |
+
annotations = pickle.load(f)
|
39 |
+
|
40 |
+
for a in annotations:
|
41 |
+
if not( ( a.get('shirt', '') and a.get('pants_or_shorts', '') ) or
|
42 |
+
( a.get('top', '') and a.get('bottom', '') ) or
|
43 |
+
a.get('dress', '')
|
44 |
+
):
|
45 |
+
print( dict_file, '\n', a )
|
46 |
+
|
47 |
+
filtered_annotations = [ a for a in annotations if ( (a.get('shirt', '') and a.get('pants_or_shorts', '')) or (
|
48 |
+
a.get('gender', '') == 'female' ) ) ]
|
49 |
+
return filtered_annotations
|
50 |
+
|
51 |
+
style_annotations_dict = {
|
52 |
+
k: get_annotations(file_path) for k, file_path in dict_files.items()
|
53 |
+
}
|
54 |
+
|
55 |
+
import collections
|
56 |
+
from collections import defaultdict
|
57 |
+
|
58 |
+
optional = [False, False, True, True, False]
|
59 |
+
article_keys = ['shirt', 'pants_or_shorts', 'hoodie_or_jacket', 'hat', 'shoes']
|
60 |
+
|
61 |
+
def annotation_to_tuple(annotation):
|
62 |
+
t = tuple( annotation[key] for key in article_keys )
|
63 |
+
return t
|
64 |
+
|
65 |
+
def remove_owned_articles(annotations_tuple, clothes_owned):
|
66 |
+
return tuple( article if article not in clothes_owned else ''
|
67 |
+
for article in annotations_tuple )
|
68 |
+
|
69 |
+
def annotations_value(annotation_tuple_count, clothes_owned):
|
70 |
+
new_scores = {}
|
71 |
+
for annotation_tuple, count in annotation_tuple_count.items():
|
72 |
+
num_missing = 0
|
73 |
+
for clothing in annotation_tuple:
|
74 |
+
|
75 |
+
if not (clothing == '' or clothing in clothes_owned):
|
76 |
+
num_missing += 1
|
77 |
+
if num_missing > 0:
|
78 |
+
new_scores[annotation_tuple] = count/num_missing
|
79 |
+
return new_scores
|
80 |
+
|
81 |
+
def is_outfit_subset(smaller_outfit, larger_outfit):
|
82 |
+
if smaller_outfit == larger_outfit:
|
83 |
+
return False
|
84 |
+
for i in range(len(larger_outfit)): # assume both r tuples of fixed size
|
85 |
+
if smaller_outfit[i] != '' and smaller_outfit[i] != larger_outfit[i]:
|
86 |
+
return False
|
87 |
+
return True
|
88 |
+
|
89 |
+
def most_outfits_helper(annotations, capacity=5, clothes_owned=[]):
|
90 |
+
annotations = [ annotation_to_tuple(e) for e in annotations ]
|
91 |
+
outfit_count = dict(collections.Counter(annotations))
|
92 |
+
|
93 |
+
outfits = list(outfit_count.keys())
|
94 |
+
for small_outfit in outfit_count:
|
95 |
+
for larger_outfit in outfit_count:
|
96 |
+
if is_outfit_subset(small_outfit, larger_outfit):
|
97 |
+
outfit_count[small_outfit] += outfit_count[larger_outfit]
|
98 |
+
|
99 |
+
clothes_owned += ['']
|
100 |
+
best_outfits = most_outfits(outfit_count, capacity, set(clothes_owned))
|
101 |
+
best_outfits.remove('')
|
102 |
+
return best_outfits
|
103 |
+
|
104 |
+
# consider every image in the dataset
|
105 |
+
# each image has an outfit and casts a vote for that outfit
|
106 |
+
# a outfit is a tuple of pants_or_short + shirt + jacket_or_hoodie?
|
107 |
+
# images with shoes or hats have an additional vote for the shoes and hats
|
108 |
+
# greedily pick the clothes with the highest votes
|
109 |
+
# then remove those clothes from all outfits in that dataset and recount
|
110 |
+
# can be solved as an integer programming problem
|
111 |
+
def most_outfits(annotation_tuple_count, capacity, clothes_owned):
|
112 |
+
if capacity == 0 :
|
113 |
+
return clothes_owned
|
114 |
+
|
115 |
+
# merge counts based on new keys
|
116 |
+
updated_annotations = defaultdict(int)
|
117 |
+
for tup, count in annotation_tuple_count.items():
|
118 |
+
updated_annotations[ remove_owned_articles(tup, clothes_owned) ] += count
|
119 |
+
annotation_tuple_count = updated_annotations
|
120 |
+
|
121 |
+
outfits_scores = annotations_value(annotation_tuple_count, clothes_owned)
|
122 |
+
outfits_scores = sorted(outfits_scores.items(), key=lambda x:-x[1])
|
123 |
+
num_new = 1
|
124 |
+
for outfit, score in outfits_scores:
|
125 |
+
clothes_proposed = clothes_owned.union(set(outfit))
|
126 |
+
num_new = len(clothes_proposed) - len(clothes_owned)
|
127 |
+
if num_new <= capacity:
|
128 |
+
clothes_owned = clothes_proposed
|
129 |
+
break
|
130 |
+
|
131 |
+
return most_outfits( annotation_tuple_count, capacity-num_new, clothes_owned )
|
132 |
+
|
133 |
+
from pyomo.environ import *
|
134 |
+
from functools import reduce
|
135 |
+
|
136 |
+
def cover_style_ip(annotations, capacity=10, clothes_owned=[], mask=None, solver='ipopt'):
|
137 |
+
"""Use integer program to find the set of clothes that makes as many outfits as possible.
|
138 |
+
annotations: List[ Dict ], contains maps from clothing categories to string types
|
139 |
+
capacity: int, number of new clothes to find
|
140 |
+
clothes_owned: List[ Str ], iterable of strings of clothing types
|
141 |
+
mask: Optional( List[ Str ] ), optional iterable of categories of clothes to consider.
|
142 |
+
Uses all clothing types by default.
|
143 |
+
solver: Str, the nonlinear optimization solver to use for max bool sat
|
144 |
+
"""
|
145 |
+
unique_clothes = set()
|
146 |
+
interested_clothing_types = article_keys if mask is None else mask
|
147 |
+
for a in annotations:
|
148 |
+
for key in interested_clothing_types:
|
149 |
+
unique_clothes.add( a[key] )
|
150 |
+
if '' in unique_clothes:
|
151 |
+
unique_clothes.remove('')
|
152 |
+
|
153 |
+
model = ConcreteModel()
|
154 |
+
|
155 |
+
# 1. Create the variables we want to optimize
|
156 |
+
clothing_literals_dict = {clothing: Var(within=Binary) for clothing in unique_clothes}
|
157 |
+
|
158 |
+
# set literals to true for clothes already owned
|
159 |
+
for clothing in clothes_owned:
|
160 |
+
clothing_literals_dict[clothing] = 1
|
161 |
+
|
162 |
+
# capacity changes from number of new clothes to total clothes
|
163 |
+
capacity += len(clothes_owned)
|
164 |
+
|
165 |
+
capacity_constraint = Constraint(expr=sum(list(clothing_literals_dict.values())) <= capacity)
|
166 |
+
|
167 |
+
outfit_clauses = []
|
168 |
+
for a in annotations:
|
169 |
+
# get the clothing literal if it exists, otherwise it's masked and say it's absent
|
170 |
+
outfit_literals = [ clothing_literals_dict.get(a[key], 0) for key in article_keys if a[key] != '' ]
|
171 |
+
outfit_clauses += [ reduce(lambda x,y: x*y, outfit_literals) ]
|
172 |
+
|
173 |
+
# 3. Maximize the objective function
|
174 |
+
objective = Objective( expr=sum( outfit_clauses ), sense=maximize)
|
175 |
+
|
176 |
+
for name, literal in clothing_literals_dict.items():
|
177 |
+
setattr(model, name, literal)
|
178 |
+
model.capacity_constraint = capacity_constraint
|
179 |
+
model.objective = objective
|
180 |
+
|
181 |
+
SolverFactory(solver).solve(model)
|
182 |
+
|
183 |
+
basis_clothes = [ name for name, literal in clothing_literals_dict.items() if not isinstance(literal, int) and round(literal()) ]
|
184 |
+
return basis_clothes
|
185 |
+
|
186 |
+
def annotation_str(annotation):
|
187 |
+
output = ""
|
188 |
+
output += annotation['hat']
|
189 |
+
output += ' + ' + annotation['shirt'] if output and annotation['shirt'] else annotation['shirt']
|
190 |
+
output += ' + ' + annotation['hoodie_or_jacket'] if output and annotation['hoodie_or_jacket'] else annotation['hoodie_or_jacket']
|
191 |
+
output += ' + ' + annotation['pants_or_shorts'] if output and annotation['pants_or_shorts'] else annotation['pants_or_shorts']
|
192 |
+
output += ' + ' + annotation['shoes'] if output and annotation['shoes'] else annotation['shoes']
|
193 |
+
# output += ' ' + annotation['url'] if annotation.get('url') else ''
|
194 |
+
return output
|
195 |
+
|
196 |
+
def annotation_str(annotation):
|
197 |
+
gender = annotation.get('gender', 'male')
|
198 |
+
output = ""
|
199 |
+
for k in gender_article_keys[gender]:
|
200 |
+
output += ' + ' + annotation[k] if output and annotation.get(k) else annotation.get(k, '')
|
201 |
+
return output
|
202 |
+
|
203 |
+
|
204 |
+
def annotation_to_key(annotation):
|
205 |
+
gender = annotation.get('gender', 'male')
|
206 |
+
output = ""
|
207 |
+
if gender == 'male':
|
208 |
+
output += ' + ' + annotation['shirt'] if output and annotation['shirt'] else annotation['shirt']
|
209 |
+
output += ' + ' + annotation['hoodie_or_jacket'] if output and annotation['hoodie_or_jacket'] else annotation['hoodie_or_jacket']
|
210 |
+
output += ' + ' + annotation['pants_or_shorts'] if output and annotation['pants_or_shorts'] else annotation['pants_or_shorts']
|
211 |
+
else:
|
212 |
+
useful_keys = ['dress', 'top', 'bottom', 'hoodie_or_jacket']
|
213 |
+
for k in useful_keys:
|
214 |
+
output += ' + ' + annotation[k] if output and annotation.get(k) else annotation.get(k, '')
|
215 |
+
return output
|
216 |
+
|
217 |
+
|
218 |
+
def get_num_outfits(annotations, articles):
|
219 |
+
outfits = set()
|
220 |
+
for e in annotations:
|
221 |
+
if (sum([(e[key] == '' or e[key] in articles ) for key in article_keys]) == len(article_keys)
|
222 |
+
# and e['shirt'] and e['pants_or_shorts']
|
223 |
+
):
|
224 |
+
str_form = annotation_to_key(e) # ignore +- hat, shoes otherwise use annotation_str
|
225 |
+
outfits.add(str_form)
|
226 |
+
return sorted(list(outfits))
|
227 |
+
|
228 |
+
def get_outfit_urls(annotations, outfits):
|
229 |
+
outfit_urls = defaultdict(list)
|
230 |
+
|
231 |
+
for e in annotations:
|
232 |
+
str_form = annotation_to_key(e)
|
233 |
+
if e.get('url') and str_form in outfits:
|
234 |
+
outfit_urls[str_form] += [ e['url'] ]
|
235 |
+
return dict(outfit_urls)
|
236 |
+
|
237 |
+
def cover_style(annotations, capacity=10, clothes_owned=[]):
|
238 |
+
clothes = list(cover_style_ip(annotations, capacity=capacity, clothes_owned=clothes_owned))
|
239 |
+
reachable_outfits = get_num_outfits(annotations, set(clothes+clothes_owned) )
|
240 |
+
|
241 |
+
if len(clothes_owned) == 0: # return basis outfits from scratch
|
242 |
+
return clothes, get_outfit_urls(annotations, reachable_outfits)
|
243 |
+
|
244 |
+
elif capacity == 0: # return reach of current clothes
|
245 |
+
return clothes, get_outfit_urls(annotations, reachable_outfits)
|
246 |
+
|
247 |
+
else: # capacity > 0 and len(clothes_owned) > 0, show new clothes and outfits
|
248 |
+
new_clothes = [ c for c in clothes if c not in clothes_owned ]
|
249 |
+
reachable_outfits_base = get_num_outfits(annotations, clothes_owned)
|
250 |
+
new_outfits = [ o for o in reachable_outfits if o not in reachable_outfits_base ]
|
251 |
+
return new_clothes, get_outfit_urls(annotations, new_outfits)
|
252 |
+
|
253 |
+
|
254 |
+
def str_to_list(input):
|
255 |
+
tokens = [ token.strip() for token in input.split(',') ]
|
256 |
+
return [ t for t in tokens if t != '' ]
|
257 |
+
|
258 |
+
CLOTHES_HEADER = f'## Most Effective Clothes'
|
259 |
+
OUTFITS_HEADER = f'## Possible ππ Outfits'
|
260 |
+
|
261 |
+
def cover_style_helper(styles, capacity=10, clothes_owned=''):
|
262 |
+
|
263 |
+
if len(styles) == 0:
|
264 |
+
return f'{CLOTHES_HEADER}\nPlease pick at least one style from the left.' , OUTFITS_HEADER
|
265 |
+
|
266 |
+
clothes_owned = str_to_list(clothes_owned)
|
267 |
+
annotations = []
|
268 |
+
for style in styles:
|
269 |
+
annotations += style_annotations_dict[style][:500]
|
270 |
+
|
271 |
+
if len(styles) == 1: # hack for h&m having wayyyyy more examples than other brands
|
272 |
+
annotations = style_annotations_dict[style]
|
273 |
+
|
274 |
+
clothes, outfit_urls = cover_style(annotations, capacity, clothes_owned)
|
275 |
+
clothes_str = f'{CLOTHES_HEADER}\n'
|
276 |
+
for c in clothes:
|
277 |
+
clothes_str += f'* {c}\n'
|
278 |
+
|
279 |
+
outfits_str = f'{OUTFITS_HEADER} ({len(outfit_urls)})\n'
|
280 |
+
|
281 |
+
for outfit, urls in outfit_urls.items():
|
282 |
+
outfits_str += f'1. {str(outfit)}: '
|
283 |
+
for i, u in enumerate(urls):
|
284 |
+
outfits_str += f'<a href="{u}" target="_blank">[{i+1}]</a>' # f'[\[{i}\]]({u})'
|
285 |
+
outfits_str += '\n'
|
286 |
+
return clothes_str, outfits_str
|
287 |
+
|
288 |
+
article_keys = gender_article_keys['male']
|
289 |
+
print(cover_style_helper( ['banana republic'] ))
|
290 |
+
|
291 |
+
def cover_style_helper_wrapper(markdown, styles, capacity=10, clothes_owned=''):
|
292 |
+
if len(styles) > 0:
|
293 |
+
global article_keys
|
294 |
+
gender = style_gender[styles[0]]
|
295 |
+
article_keys = gender_article_keys[gender]
|
296 |
+
return cover_style_helper(styles, capacity, clothes_owned)
|
297 |
+
|
298 |
+
def filter_test(annotation, filter_set):
|
299 |
+
for f in filter_set:
|
300 |
+
num_occur = sum([1 for key in article_keys+['caption'] if f in annotation[key]])
|
301 |
+
if num_occur == 0:
|
302 |
+
return False
|
303 |
+
return True
|
304 |
+
|
305 |
+
import gradio as gr
|
306 |
+
|
307 |
+
def change_gallery(style_choice, start_from=0, text_filter=''):
|
308 |
+
global article_keys
|
309 |
+
gender = style_gender[style_choice]
|
310 |
+
article_keys = gender_article_keys[gender]
|
311 |
+
|
312 |
+
chosen_annotations = style_annotations_dict[style_choice]
|
313 |
+
if text_filter:
|
314 |
+
text_filter = set([t.strip() for t in text_filter.split(',')])
|
315 |
+
chosen_annotations = [ a for a in chosen_annotations if filter_test(a, text_filter) ]
|
316 |
+
start_from = int(start_from/100*len(chosen_annotations))
|
317 |
+
# print(len(chosen_annotations), [a['url'] for a in chosen_annotations[start_from:start_from+20]])
|
318 |
+
return [a['url'] for a in chosen_annotations[start_from:start_from+20]]
|
319 |
+
|
320 |
+
def update_styles(gender):
|
321 |
+
global article_keys
|
322 |
+
article_keys = gender_article_keys[gender]
|
323 |
+
|
324 |
+
default_values = ["banana republic"] if gender == "male" else ["aritzia"]
|
325 |
+
return gr.CheckboxGroup.update(choices=[k for k in style_annotations_dict.keys() if style_gender[k] == gender], value=default_values, label='Styles')
|
326 |
+
|
327 |
+
|
328 |
+
|
329 |
+
|
330 |
+
|
331 |
+
article_keys = gender_article_keys['male']
|
332 |
+
INTRO_MARKDOWN = """**Good clothes are the ones that you can use to make many outfits.**
|
333 |
+
Finding stuff that works with your style and wardrobe is hard.
|
334 |
+
This program helps you find the clothes that make as many fashionable outfits as possible,
|
335 |
+
given your current wardrobe, style, and budget.
|
336 |
+
|
337 |
+
"""
|
338 |
+
|
339 |
+
with gr.Blocks() as demo:
|
340 |
+
with gr.Tabs(selected=0):
|
341 |
+
with gr.TabItem('Find the Literal Optimal Clothes', id=0):
|
342 |
+
with gr.Box():
|
343 |
+
default_clothes = """black dress pants,
|
344 |
+
blue dress shirt,
|
345 |
+
blue jacket,
|
346 |
+
blue jeans,
|
347 |
+
blue shirt,
|
348 |
+
brown boots,
|
349 |
+
gray sweater,
|
350 |
+
white dress shirt,
|
351 |
+
white sneakers"""
|
352 |
+
|
353 |
+
with gr.Row():
|
354 |
+
with gr.Column():
|
355 |
+
gr.Markdown(INTRO_MARKDOWN)
|
356 |
+
with gr.Row():
|
357 |
+
with gr.Group():
|
358 |
+
gender = gr.Radio(["male", "female"], value="male", label='gender')
|
359 |
+
styles = gr.CheckboxGroup([k for k in style_annotations_dict.keys() if style_gender[k] == gender.value], value=["banana republic"], label='Styles')
|
360 |
+
gender.change(update_styles, inputs=[gender], outputs=[styles])
|
361 |
+
with gr.Group():
|
362 |
+
capacity = gr.Number(5, precision=0, label='Number of clothes to recommend')
|
363 |
+
clothes_owned = gr.Textbox(
|
364 |
+
label="Clothes Owned",
|
365 |
+
lines=3,
|
366 |
+
value=default_clothes, #json.dumps(default_clothes),
|
367 |
+
)
|
368 |
+
with gr.Row():
|
369 |
+
btn = gr.Button("Recommend Clothes")
|
370 |
+
|
371 |
+
with gr.Row():
|
372 |
+
clothes_markdown, outfits_markdown = cover_style_helper(styles.value, capacity.value, clothes_owned.value)
|
373 |
+
clothes_recommended = gr.Markdown(clothes_markdown)
|
374 |
+
possible_outfits = gr.Markdown(outfits_markdown)
|
375 |
+
|
376 |
+
btn.click(cover_style_helper, inputs=[styles, capacity, clothes_owned], outputs=[clothes_recommended, possible_outfits])
|
377 |
+
|
378 |
+
gr.Markdown("## 3 Different Example Uses")
|
379 |
+
example_label = gr.Textbox('', label='Explanation', visible=False)
|
380 |
+
gr.Examples(examples=[ ['Find the central clothes that make a style.', ['street'], 10, ''],
|
381 |
+
['Find new outfits hidden in your wardrobe.', ['h&m'], 0, 'white t shirt, grey t shirt, black pants, black shorts, black t shirt, blue jeans'],
|
382 |
+
['Find the clothes that best fit your existing wardrobe.', ['banana republic', 'street'], 5, 'white t shirt, grey t shirt, black pants, black shorts, black t shirt, blue jeans']],
|
383 |
+
inputs=[example_label, styles, capacity, clothes_owned],
|
384 |
+
outputs=[clothes_recommended, possible_outfits],
|
385 |
+
fn=cover_style_helper_wrapper,
|
386 |
+
cache_examples=False)
|
387 |
+
|
388 |
+
EXPLORE_MARKDOWN = """# Explore Styles
|
389 |
+
Hint: Click an image to enable arrow key browsing.
|
390 |
+
You put in clothes you own to find outfit ideas!
|
391 |
+
"""
|
392 |
+
with gr.TabItem('Explore Styles', id=1):
|
393 |
+
with gr.Box():
|
394 |
+
gr.Markdown(EXPLORE_MARKDOWN)
|
395 |
+
with gr.Row():
|
396 |
+
with gr.Column():
|
397 |
+
bad_urls = ['dynamite', 'abercrombie women']
|
398 |
+
styles = gr.Radio(list(style_annotations_dict.keys()), value="uniqlo", label='Styles')
|
399 |
+
start_from = gr.Slider(0, 100, label='Start from', value=0, step=10)
|
400 |
+
with gr.Group():
|
401 |
+
text_filter = gr.Textbox(value='white t shirt, jeans', label='Outfit includes')
|
402 |
+
filter_button = gr.Button('Apply Text Filter')
|
403 |
+
with gr.Column():
|
404 |
+
outfits_gallery = gr.Gallery(label='Outfit Examples', value=change_gallery(styles.value, start_from.value, text_filter.value))
|
405 |
+
|
406 |
+
styles.change(fn=change_gallery, inputs=[styles, start_from, text_filter], outputs=outfits_gallery)
|
407 |
+
start_from.change(fn=change_gallery, inputs=[styles, start_from, text_filter], outputs=outfits_gallery)
|
408 |
+
filter_button.click(fn=change_gallery, inputs=[styles, start_from, text_filter], outputs=outfits_gallery)
|
409 |
+
|
410 |
+
with gr.TabItem('How it works', id=2):
|
411 |
+
ABOUT_MARKDOWN = """
|
412 |
+
# Why
|
413 |
+
I don't really enjoy shopping, it takes a while a look at all the clothes,
|
414 |
+
and I'm not sure if I should buy what I picked out.
|
415 |
+
This program can tell me which clothes are the best for my wardrobe and style,
|
416 |
+
so I don't have to worry about buying the wrong stuff.
|
417 |
+
|
418 |
+
# How
|
419 |
+
This program poses the problem as a nonlinear integer programming problem.
|
420 |
+
The problem is to maximize the number of outfits, while contraining the number of clothes to be <= "this max you want to buy".
|
421 |
+
Let a, b, c etc be binary literals that represent the use of clothing type a, b, c.
|
422 |
+
These are clothing types like white dress shirt, or black hat.
|
423 |
+
We maximize the nonlinear expression abc + ade + bec + ... similar to a disjuctive normal form,
|
424 |
+
where each clause represents an outfit and is the product of clothing literals.
|
425 |
+
Each outfit clause has value 1 iff every article of clothing is used in the solution.
|
426 |
+
Pyomo is used to solve this optimization problem.
|
427 |
+
|
428 |
+
This objective takes the form of something like,
|
429 |
+
<iframe src="https://drive.google.com/file/d/1j0bBRZkdc7Ce5KWawlwrV36lI0MsMLRf/preview" width="640" height="480" allow="autoplay"></iframe>
|
430 |
+
|
431 |
+
Hart, William E., Carl Laird, Jean-Paul Watson, David L. Woodruff, Gabriel A. Hackebeil, Bethany L. Nicholson, and John D. Siirola. Pyomo β Optimization Modeling in Python. Springer, 2017.
|
432 |
+
|
433 |
+
|
434 |
+
# Caveats
|
435 |
+
This model understands fashion from a macro level.
|
436 |
+
It understands what a white shirt is, but is entirely blind to micro features like fit, brand, or shape.
|
437 |
+
It also bins different kinds of clothing and colors together.
|
438 |
+
So the model could mix up light grey sweatpants and dark grey chinos because they're both grey pants.
|
439 |
+
|
440 |
+
There is also error introduced from bad image annotations, approximate solvers, and style dataset size.
|
441 |
+
|
442 |
+
Hence it's important to look at the outfits images under possible outfits for a human touch!
|
443 |
+
"""
|
444 |
+
with gr.Box():
|
445 |
+
gr.Markdown(ABOUT_MARKDOWN)
|
446 |
+
|
447 |
+
|
448 |
+
|
449 |
+
demo.launch(debug=True)
|
450 |
+
|
451 |
+
|
452 |
+
|
objective.PNG
ADDED
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
pyomo
|