m3hrdadfi commited on
Commit
1b377b0
1 Parent(s): 74a6291

Transfer from git - HC

Browse files
CONTRIBUTING.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1. Fork the repository by clicking on the ``Fork`` button on the repository's page. This creates a copy of the code under your GitHub user account.
2
+
3
+ 2. Clone your fork to your local disk, and add the base repository as a remote.
4
+ ```bash
5
+ $ git clone git@github.com:<your-GitHub-username>/chef-transformer.git
6
+ $ cd chef-transformer
7
+ $ git remote add upstream https://github.com/chef-transformer/chef-transformer.git
8
+ ```
9
+
10
+ 3. Create a new branch to hold your development changes.
11
+ ```bash
12
+ $ git checkout -b a-descriptive-name-for-your-changes
13
+ ```
14
+
15
+ > NOTE: Do not work on the ``main`` branch.
16
+
17
+ 4. Set up a development environment by running the following command in a virtual environment.
18
+ ```bash
19
+ $ pip install -r requirements.txt
20
+ ```
21
+
22
+ 5. DEVELOP THE CODE
23
+
24
+ 6. It is a good idea to sync your copy of the code with the original repository regularly. This way you can quickly account for changes.
25
+ ```bash
26
+ $ git fetch upstream
27
+ $ git rebase upstream/main
28
+ ```
29
+
30
+ 7. Push the changes to your account using:
31
+ ```bash
32
+ $ git push -u origin a-descriptive-name-for-your-changes
33
+ ```
34
+
35
+ 8. Once you are satisfied (and the checklist above is happy too), go to the webpage of your fork on GitHub. Click on ``Pull Request`` to send your changes to the project maintainers for review.
README.md CHANGED
@@ -8,10 +8,233 @@ app_file: app.py
8
  pinned: false
9
  ---
10
 
11
- # Streamlit demo for Chef Transformers
 
12
 
 
13
 
14
- ### Launch demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  streamlit run app.py
17
- ```
 
 
 
 
 
 
 
 
 
 
 
 
8
  pinned: false
9
  ---
10
 
11
+ # Chef Transformer (T5)
12
+ > This is part of the [Flax/Jax Community Week](https://discuss.huggingface.co/t/recipe-generation-model/7475), organized by [HuggingFace](https://huggingface.co/) and TPU usage sponsored by Google.
13
 
14
+ Want to give it a try? Then what's the wait, head over to the demo [here](https://share.streamlit.io/chef-transformer/chef-transformer/main/app.py).
15
 
16
+
17
+ ## Team Members
18
+ - Mehrdad Farahani ([m3hrdadfi](https://huggingface.co/m3hrdadfi))
19
+ - Kartik Godawat ([dk-crazydiv](https://huggingface.co/dk-crazydiv))
20
+ - Haswanth Aekula ([hassiahk](https://huggingface.co/hassiahk))
21
+ - Deepak Pandian ([rays2pix](https://huggingface.co/rays2pix))
22
+ - Nicholas Broad ([nbroad](https://huggingface.co/nbroad))
23
+
24
+ ## Dataset
25
+
26
+ [RecipeNLG: A Cooking Recipes Dataset for Semi-Structured Text Generation](https://recipenlg.cs.put.poznan.pl/). This dataset contains **2,231,142** cooking recipes (>2 millions) with size of **2.14 GB**. It's processed in more careful way.
27
+
28
+ ### Example
29
+
30
+ ```json
31
+ {
32
+ "NER": [
33
+ "oyster crackers",
34
+ "salad dressing",
35
+ "lemon pepper",
36
+ "dill weed",
37
+ "garlic powder",
38
+ "salad oil"
39
+ ],
40
+ "directions": [
41
+ "Combine salad dressing mix and oil.",
42
+ "Add dill weed, garlic powder and lemon pepper.",
43
+ "Pour over crackers; stir to coat.",
44
+ "Place in warm oven.",
45
+ "Use very low temperature for 15 to 20 minutes."
46
+ ],
47
+ "ingredients": [
48
+ "12 to 16 oz. plain oyster crackers",
49
+ "1 pkg. Hidden Valley Ranch salad dressing mix",
50
+ "1/4 tsp. lemon pepper",
51
+ "1/2 to 1 tsp. dill weed",
52
+ "1/4 tsp. garlic powder",
53
+ "3/4 to 1 c. salad oil"
54
+ ],
55
+ "link": "www.cookbooks.com/Recipe-Details.aspx?id=648947",
56
+ "source": "Gathered",
57
+ "title": "Hidden Valley Ranch Oyster Crackers"
58
+ }
59
  ```
60
+
61
+ ## How To Use
62
+
63
+ ```bash
64
+ # Installing requirements
65
+ pip install transformers
66
+ ```
67
+
68
+ ```python
69
+ from transformers import FlaxAutoModelForSeq2SeqLM
70
+ from transformers import AutoTokenizer
71
+
72
+ MODEL_NAME_OR_PATH = "flax-community/t5-recipe-generation"
73
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME_OR_PATH, use_fast=True)
74
+ model = FlaxAutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME_OR_PATH)
75
+
76
+ prefix = "items: "
77
+ # generation_kwargs = {
78
+ # "max_length": 1024,
79
+ # "min_length": 128,
80
+ # "no_repeat_ngram_size": 3,
81
+ # "do_sample": True,
82
+ # "top_k": 60,
83
+ # "top_p": 0.95
84
+ # }
85
+ generation_kwargs = {
86
+ "max_length": 512,
87
+ "min_length": 64,
88
+ "no_repeat_ngram_size": 3,
89
+ "early_stopping": True,
90
+ "num_beams": 5,
91
+ "length_penalty": 1.5,
92
+ }
93
+
94
+ special_tokens = tokenizer.all_special_tokens
95
+ tokens_map = {
96
+ "<sep>": "--",
97
+ "<section>": "\n"
98
+ }
99
+ def skip_special_tokens(text, special_tokens):
100
+ for token in special_tokens:
101
+ text = text.replace(token, "")
102
+
103
+ return text
104
+
105
+ def target_postprocessing(texts, special_tokens):
106
+ if not isinstance(texts, list):
107
+ texts = [texts]
108
+
109
+ new_texts = []
110
+ for text in texts:
111
+ text = skip_special_tokens(text, special_tokens)
112
+
113
+ for k, v in tokens_map.items():
114
+ text = text.replace(k, v)
115
+
116
+ new_texts.append(text)
117
+
118
+ return new_texts
119
+
120
+ def generation_function(texts):
121
+ _inputs = texts if isinstance(texts, list) else [texts]
122
+ inputs = [prefix + inp for inp in _inputs]
123
+ inputs = tokenizer(
124
+ inputs,
125
+ max_length=256,
126
+ padding="max_length",
127
+ truncation=True,
128
+ return_tensors="jax"
129
+ )
130
+
131
+ input_ids = inputs.input_ids
132
+ attention_mask = inputs.attention_mask
133
+
134
+ output_ids = model.generate(
135
+ input_ids=input_ids,
136
+ attention_mask=attention_mask,
137
+ **generation_kwargs
138
+ )
139
+ generated = output_ids.sequences
140
+ generated_recipe = target_postprocessing(
141
+ tokenizer.batch_decode(generated, skip_special_tokens=False),
142
+ special_tokens
143
+ )
144
+ return generated_recipe
145
+ ```
146
+
147
+ ```python
148
+ items = [
149
+ "macaroni, butter, salt, bacon, milk, flour, pepper, cream corn",
150
+ "provolone cheese, bacon, bread, ginger"
151
+ ]
152
+ generated = generation_function(items)
153
+ for text in generated:
154
+ sections = text.split("\n")
155
+ for section in sections:
156
+ section = section.strip()
157
+ if section.startswith("title:"):
158
+ section = section.replace("title:", "")
159
+ headline = "TITLE"
160
+ elif section.startswith("ingredients:"):
161
+ section = section.replace("ingredients:", "")
162
+ headline = "INGREDIENTS"
163
+ elif section.startswith("directions:"):
164
+ section = section.replace("directions:", "")
165
+ headline = "DIRECTIONS"
166
+
167
+ if headline == "TITLE":
168
+ print(f"[{headline}]: {section.strip().capitalize()}")
169
+ else:
170
+ section_info = [f" - {i+1}: {info.strip().capitalize()}" for i, info in enumerate(section.split("--"))]
171
+ print(f"[{headline}]:")
172
+ print("\n".join(section_info))
173
+
174
+ print("-" * 130)
175
+ ```
176
+
177
+ Output:
178
+ ```text
179
+ [TITLE]: Macaroni and corn
180
+ [INGREDIENTS]:
181
+ - 1: 2 c. macaroni
182
+ - 2: 2 tbsp. butter
183
+ - 3: 1 tsp. salt
184
+ - 4: 4 slices bacon
185
+ - 5: 2 c. milk
186
+ - 6: 2 tbsp. flour
187
+ - 7: 1/4 tsp. pepper
188
+ - 8: 1 can cream corn
189
+ [DIRECTIONS]:
190
+ - 1: Cook macaroni in boiling salted water until tender.
191
+ - 2: Drain.
192
+ - 3: Melt butter in saucepan.
193
+ - 4: Blend in flour, salt and pepper.
194
+ - 5: Add milk all at once.
195
+ - 6: Cook and stir until thickened and bubbly.
196
+ - 7: Stir in corn and bacon.
197
+ - 8: Pour over macaroni and mix well.
198
+ ----------------------------------------------------------------------------------------------------------------------------------
199
+ [TITLE]: Grilled provolone and bacon sandwich
200
+ [INGREDIENTS]:
201
+ - 1: 2 slices provolone cheese
202
+ - 2: 2 slices bacon
203
+ - 3: 2 slices sourdough bread
204
+ - 4: 2 slices pickled ginger
205
+ [DIRECTIONS]:
206
+ - 1: Place a slice of provolone cheese on one slice of bread.
207
+ - 2: Top with a slice of bacon.
208
+ - 3: Top with a slice of pickled ginger.
209
+ - 4: Top with the other slice of bread.
210
+ - 5: Heat a skillet over medium heat.
211
+ - 6: Place the sandwich in the skillet and cook until the cheese is melted and the bread is golden brown.
212
+ ----------------------------------------------------------------------------------------------------------------------------------
213
+ ```
214
+
215
+ ## Evaluation
216
+
217
+ The following table summarizes the scores obtained by the **Chef Transformer**. Those marked as (*) are the baseline models.
218
+
219
+ | Model | WER | COSIM | ROUGE-2 |
220
+ | :-------------: | :---: | :---: | :-----: |
221
+ | Recipe1M+ * | 0.786 | 0.589 | - |
222
+ | RecipeNLG * | 0.751 | 0.666 | - |
223
+ | ChefTransformer | 0.709 | 0.714 | 0.290 |
224
+
225
+ ## Streamlit demo
226
+
227
+ ```bash
228
  streamlit run app.py
229
+ ```
230
+
231
+ ## Looking to contribute?
232
+ Then follow the steps mentioned in this [contributing guide](CONTRIBUTING.md) and you are good to go.
233
+
234
+ ## Copyright
235
+
236
+ Special thanks to those who provided these fantastic materials.
237
+ - [Anatomy](https://www.flaticon.com/free-icon)
238
+ - [Chef Hat](https://www.vecteezy.com/members/jellyfishwater)
239
+ - [Moira Nazzari](https://pixabay.com/photos/food-dessert-cake-eggs-butter-3048440/)
240
+ - [Instagram Post](https://www.freepik.com/free-psd/recipes-ad-social-media-post-template_11520617.htm)
app.py CHANGED
@@ -18,9 +18,15 @@ import textwrap
18
  from examples import EXAMPLES
19
  import dummy
20
  import meta
21
- from utils import (
 
 
 
22
  remote_css,
23
  local_css,
 
 
 
24
  load_image_from_url,
25
  load_image_from_local,
26
  image_to_base64,
@@ -28,104 +34,6 @@ from utils import (
28
  )
29
 
30
 
31
- def generate_cook_image(query, app_id, app_key):
32
- api_url = f"https://api.edamam.com/api/recipes/v2?type=public&q={query}&app_id={app_id}&app_key={app_key}&field=image"
33
-
34
- try:
35
- r = requests.get(api_url)
36
- if r.status_code != 200:
37
- return None
38
-
39
- rj = r.json()
40
- if "hits" not in rj or not len(rj["hits"]) > 0:
41
- return None
42
-
43
- data = rj["hits"]
44
- data = data[random.randint(1, min(5, len(data) - 1))] if len(data) > 1 else data[0]
45
-
46
- if "recipe" not in data or "image" not in data["recipe"]:
47
- return None
48
-
49
- image = data["recipe"]["image"]
50
- return image
51
- except Exception as e:
52
- return None
53
-
54
-
55
- def generate_food_with_logo_image(bg_path, logo_path, food_url, no_food="asset/frame/no_food.png"):
56
- bg = Image.open(bg_path)
57
- width, height = bg.size
58
-
59
- logo = Image.open(logo_path)
60
- logo_width, logo_height, logo_ratio, logo_rb, logo_mb = logo.size + (3, -20, 45)
61
- logo_width, logo_height = (logo_width // logo_ratio, logo_height // logo_ratio)
62
- logo = logo.resize((logo_width, logo_height))
63
-
64
- food = load_image_from_url(food_url, rgba_mode=True, default_image=no_food)
65
-
66
- food_width, food_height = (300, 300)
67
- food = food.resize((food_width, food_height))
68
-
69
- bg.paste(food, (0, 0), food)
70
- bg.paste(logo, (width - logo_width - logo_rb, height - logo_height - logo_mb), logo)
71
-
72
- return bg
73
-
74
-
75
- def generate_recipe_image(
76
- recipe_data,
77
- bg_path,
78
- food_logo_ia,
79
- fonts,
80
- bg_color="#ffffff"
81
- ):
82
- bg = Image.open(bg_path)
83
- bg.paste(food_logo_ia, (50, 50), food_logo_ia)
84
- bg_color = Image.new("RGBA", bg.size, bg_color)
85
- bg_color.paste(bg, mask=bg)
86
-
87
- im_editable = ImageDraw.Draw(bg_color)
88
- im_editable.text(
89
- (418, 30),
90
- textwrap.fill(recipe_data["title"], 15).replace(" \n", "\n"),
91
- (61, 61, 70),
92
- font=fonts["title"],
93
- )
94
-
95
- im_editable.text(
96
- (100, 450),
97
- "Ingredients",
98
- (61, 61, 70),
99
- font=fonts["body_bold"],
100
- )
101
- ingredients = recipe_data["ingredients"]
102
- ingredients = [textwrap.fill(item, 30).replace("\n", "\n ") for item in ingredients]
103
-
104
- im_editable.text(
105
- (50, 520),
106
- "\n".join([f"- {item}" for item in ingredients]),
107
- (61, 61, 70),
108
- font=fonts["body"],
109
- )
110
-
111
- im_editable.text(
112
- (700, 450),
113
- "Directions",
114
- (61, 61, 70),
115
- font=fonts["body_bold"],
116
- )
117
-
118
- directions = recipe_data["directions"]
119
- directions = [textwrap.fill(item, 70).replace("\n", "\n ") for item in directions]
120
- im_editable.text(
121
- (430, 520),
122
- "\n".join([f"{i + 1}. {item}" for i, item in enumerate(directions)]).strip(),
123
- (61, 61, 70),
124
- font=fonts["body"],
125
- )
126
- return bg_color
127
-
128
-
129
  class TextGeneration:
130
  def __init__(self):
131
  self.debug = False
@@ -215,6 +123,7 @@ class TextGeneration:
215
  return frame
216
 
217
  def generate(self, items, generation_kwargs):
 
218
  recipe = self.dummy_outputs[random.randint(0, len(self.dummy_outputs) - 1)]
219
 
220
  if not self.debug:
@@ -291,9 +200,10 @@ def main():
291
  # else:
292
  # get_random_frame = generator.frames[0]
293
 
 
294
  local_css("asset/css/style.css")
295
 
296
- col1, col2 = st.beta_columns([4, 3])
297
  with col2:
298
  st.image(load_image_from_local("asset/images/chef-transformer-transparent.png"), width=300)
299
  st.markdown(meta.SIDEBAR_INFO, unsafe_allow_html=True)
@@ -321,12 +231,13 @@ def main():
321
  prompt_box = EXAMPLES[prompt]
322
 
323
  items = st.text_area(
324
- 'Insert your ingredients here (separated by `,`): ',
325
  pure_comma_separation(prompt_box, return_list=False),
326
  )
327
  items = pure_comma_separation(items, return_list=False)
328
  entered_items = st.empty()
329
- recipe_button = st.button('Get Recipe!')
 
330
 
331
  st.markdown(
332
  "<hr />",
@@ -354,14 +265,21 @@ def main():
354
  food_image = generated_recipe["image"]
355
  food_image = load_image_from_url(food_image, rgba_mode=True, default_image=generator.no_food)
356
  food_image = image_to_base64(food_image)
357
- ingredients = generated_recipe["ingredients"]
 
 
 
 
 
358
  directions = [textwrap.fill(item, 70).replace("\n", "\n ") for item in
359
  generated_recipe["directions"]]
 
 
360
  generated_recipe["by"] = chef
361
 
362
- r1, r2 = st.beta_columns([3, 5])
363
 
364
- with r1:
365
  # st.write(st.session_state.get_random_frame)
366
  # if hasattr(st, "session_state"):
367
  # recipe_post = generator.generate_frame(generated_recipe, st.session_state.get_random_frame)
@@ -378,21 +296,21 @@ def main():
378
  output_format="PNG"
379
  )
380
 
381
- with r2:
382
  st.markdown(
383
  " ".join([
384
  "<div class='r-text-recipe'>",
385
  "<div class='food-title'>",
386
  f"<img src='{food_image}' />",
387
- f"<h2>{title}</h2>",
388
  "</div>",
389
  '<div class="divider"><div class="divider-mask"></div></div>',
390
- "<h3>Ingredients</h3>",
391
- "<ul class='ingredients-list'>",
392
  " ".join([f'<li>{item}</li>' for item in ingredients]),
393
  "</ul>",
394
- "<h3>Directions</h3>",
395
- "<ol class='ingredients-list'>",
396
  " ".join([f'<li>{item}</li>' for item in directions]),
397
  "</ol>",
398
  "</div>"
 
18
  from examples import EXAMPLES
19
  import dummy
20
  import meta
21
+ from utils import ext
22
+ from utils.api import generate_cook_image
23
+ from utils.draw import generate_food_with_logo_image, generate_recipe_image
24
+ from utils.st import (
25
  remote_css,
26
  local_css,
27
+
28
+ )
29
+ from utils.utils import (
30
  load_image_from_url,
31
  load_image_from_local,
32
  image_to_base64,
 
34
  )
35
 
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  class TextGeneration:
38
  def __init__(self):
39
  self.debug = False
 
123
  return frame
124
 
125
  def generate(self, items, generation_kwargs):
126
+ recipe = self.dummy_outputs[0]
127
  recipe = self.dummy_outputs[random.randint(0, len(self.dummy_outputs) - 1)]
128
 
129
  if not self.debug:
 
200
  # else:
201
  # get_random_frame = generator.frames[0]
202
 
203
+ remote_css("https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600&family=Poppins:wght@600&display=swap")
204
  local_css("asset/css/style.css")
205
 
206
+ col1, col2 = st.beta_columns([6, 4])
207
  with col2:
208
  st.image(load_image_from_local("asset/images/chef-transformer-transparent.png"), width=300)
209
  st.markdown(meta.SIDEBAR_INFO, unsafe_allow_html=True)
 
231
  prompt_box = EXAMPLES[prompt]
232
 
233
  items = st.text_area(
234
+ 'Insert your food items here (separated by `,`): ',
235
  pure_comma_separation(prompt_box, return_list=False),
236
  )
237
  items = pure_comma_separation(items, return_list=False)
238
  entered_items = st.empty()
239
+
240
+ recipe_button = st.button('Get Recipe!')
241
 
242
  st.markdown(
243
  "<hr />",
 
265
  food_image = generated_recipe["image"]
266
  food_image = load_image_from_url(food_image, rgba_mode=True, default_image=generator.no_food)
267
  food_image = image_to_base64(food_image)
268
+
269
+ ingredients = ext.ingredients(
270
+ generated_recipe["ingredients"],
271
+ pure_comma_separation(items, return_list=True)
272
+ )
273
+
274
  directions = [textwrap.fill(item, 70).replace("\n", "\n ") for item in
275
  generated_recipe["directions"]]
276
+ directions = ext.directions(directions)
277
+
278
  generated_recipe["by"] = chef
279
 
280
+ r1, r2 = st.beta_columns([6, 2])
281
 
282
+ with r2:
283
  # st.write(st.session_state.get_random_frame)
284
  # if hasattr(st, "session_state"):
285
  # recipe_post = generator.generate_frame(generated_recipe, st.session_state.get_random_frame)
 
296
  output_format="PNG"
297
  )
298
 
299
+ with r1:
300
  st.markdown(
301
  " ".join([
302
  "<div class='r-text-recipe'>",
303
  "<div class='food-title'>",
304
  f"<img src='{food_image}' />",
305
+ f"<h2 class='font-title text-bold'>{title}</h2>",
306
  "</div>",
307
  '<div class="divider"><div class="divider-mask"></div></div>',
308
+ "<h3 class='ingredients font-body text-bold'>Ingredients</h3>",
309
+ "<ul class='ingredients-list font-body'>",
310
  " ".join([f'<li>{item}</li>' for item in ingredients]),
311
  "</ul>",
312
+ "<h3 class='directions font-body text-bold'>Directions</h3>",
313
+ "<ol class='ingredients-list font-body'>",
314
  " ".join([f'<li>{item}</li>' for item in directions]),
315
  "</ol>",
316
  "</div>"
asset/css/style.css CHANGED
@@ -2,6 +2,18 @@ body {
2
  background-color: #fff;
3
  }
4
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  .fullScreenFrame > div {
7
  display: flex;
@@ -29,7 +41,7 @@ body {
29
  }
30
  .contributors a.contributor {
31
  text-decoration: none;
32
- color: #9b9dad;
33
  }
34
  .contributors a.contributor:hover {
35
  text-decoration: underline;
@@ -37,17 +49,24 @@ body {
37
 
38
  .story-box {
39
  overflow-y: scroll;
40
- max-height: 300px;
 
 
 
 
 
 
 
41
  }
42
 
43
  .r-text-recipe {
44
- padding-left: 30px;
45
- margin-left: 10px;
46
- border-left: 1px dashed #eee;
47
  }
48
 
49
  .divider {
50
- margin: 5px auto;
51
  width: 400px;
52
  max-width: 100%;
53
  position:relative;
@@ -61,23 +80,53 @@ body {
61
  .divider-mask:after {
62
  content: '';
63
  display: block;
64
- margin: 0 auto;
65
  width: 170px;
66
  height: 0px;
67
  border-bottom: 2px solid #e9a726;
68
  border-radius: 10px;
 
69
  }
70
 
71
  .r-text-recipe .food-title {
72
- text-align: center;
73
  }
74
  .r-text-recipe .food-title img {
75
- max-width: 120px;
 
 
 
76
  }
77
  .r-text-recipe .food-title h2 {
78
  }
 
79
  .ingredients-list {
80
  columns: 2;
81
  -webkit-columns: 2;
82
  -moz-columns: 2;
83
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  background-color: #fff;
3
  }
4
 
5
+ .font-title {
6
+ font-family: 'Poppins', sans-serif !important;
7
+ }
8
+ .font-body {
9
+ font-family: 'Montserrat', sans-serif !important;
10
+ }
11
+ .text-bold {
12
+ font-weight: normal !important;
13
+ }
14
+ .text-bold {
15
+ font-weight: bold !important;
16
+ }
17
 
18
  .fullScreenFrame > div {
19
  display: flex;
 
41
  }
42
  .contributors a.contributor {
43
  text-decoration: none;
44
+ color: #585858;
45
  }
46
  .contributors a.contributor:hover {
47
  text-decoration: underline;
 
49
 
50
  .story-box {
51
  overflow-y: scroll;
52
+ max-height: 240px;
53
+ }
54
+
55
+ .story-box p {
56
+ font-size: 0.85rem;
57
+ }
58
+ .story-box pre {
59
+ font-size: 0.6rem;
60
  }
61
 
62
  .r-text-recipe {
63
+ /* padding-left: 30px;
64
+ margin-left: 10px;*/
65
+ border-right: 1px dashed #eee;
66
  }
67
 
68
  .divider {
69
+ margin: 5px 0;
70
  width: 400px;
71
  max-width: 100%;
72
  position:relative;
 
80
  .divider-mask:after {
81
  content: '';
82
  display: block;
 
83
  width: 170px;
84
  height: 0px;
85
  border-bottom: 2px solid #e9a726;
86
  border-radius: 10px;
87
+ left: 0px;
88
  }
89
 
90
  .r-text-recipe .food-title {
91
+ text-align: left;
92
  }
93
  .r-text-recipe .food-title img {
94
+ max-width: 300px;
95
+ float: left;
96
+ margin-right: 30px;
97
+ margin-bottom: 30px;
98
  }
99
  .r-text-recipe .food-title h2 {
100
  }
101
+ .ingredients {}
102
  .ingredients-list {
103
  columns: 2;
104
  -webkit-columns: 2;
105
  -moz-columns: 2;
106
  }
107
+ .directions {
108
+ clear: both;
109
+ float: none;
110
+ padding-top: 20px;
111
+ display: block;
112
+ }
113
+ .directions-list {}
114
+
115
+
116
+ @media only screen and (max-width: 600px) {
117
+ .r-text-recipe {
118
+ border-right: 0;
119
+ border-bottom: 1px dashed #eee;
120
+ }
121
+ .r-text-recipe .food-title img {
122
+ max-width: 200px;
123
+ }
124
+ .directions {
125
+ padding-top: 0px;
126
+ }
127
+ .ingredients-list {
128
+ columns: 1;
129
+ -webkit-columns: 1;
130
+ -moz-columns: 1;
131
+ }
132
+ }
meta.py CHANGED
@@ -1,6 +1,6 @@
1
  HEADER_INFO = """""".strip()
2
  SIDEBAR_INFO = """
3
- <div class="contributors">
4
  <a class="contributor comma" href="https://huggingface.co/m3hrdadfi">Mehrdad Farahani</a>
5
  <a class="contributor comma" href="https://huggingface.co/dk-crazydiv">Kartik Godawat</a>
6
  <a class="contributor comma" href="https://huggingface.co/hassiahk">Haswanth Aekula</a>
@@ -9,13 +9,15 @@ SIDEBAR_INFO = """
9
  </div>
10
  """
11
  CHEF_INFO = """
12
- <p class="strong">Welcome to our lovely restaurant!
 
13
  <span class="d-block extra-info">(We are at your service with two of the best chefs in the world: Chef Scheherazade,
14
- Chef Giovanni. Scheherazade is known for being more creative whereas Giovanni is more meticulous.)</span></p>
 
15
  """.strip()
16
  PROMPT_BOX = "Add custom ingredients here (separated by `,`): "
17
  STORY = """
18
- <div class="story-box">
19
  <p>
20
  Hello everyone 👋, I am <strong>Chef Transformer</strong>,
21
  the owner of this restaurant. I was made by a group of <a href="https://huggingface.co/flax-community/t5-recipe-generation#team-members">NLP Engineers</a> to train my two prodigy recipe creators: <strong>Chef Scheherazade</strong> and <strong>Chef Giovanni</strong>.
@@ -28,8 +30,7 @@ The NLP engineers helped guide the learning process so that the chefs could actu
28
  I trained my chefs by asking them to generate a title, a list of ingredients (including amounts!), and a list of directions after giving them just a simple list of food items.
29
  </p>
30
 
31
- <pre>
32
- [Input]
33
  {food items*: separated by comma}
34
 
35
  [Targets]
 
1
  HEADER_INFO = """""".strip()
2
  SIDEBAR_INFO = """
3
+ <div class="contributors font-body text-bold">
4
  <a class="contributor comma" href="https://huggingface.co/m3hrdadfi">Mehrdad Farahani</a>
5
  <a class="contributor comma" href="https://huggingface.co/dk-crazydiv">Kartik Godawat</a>
6
  <a class="contributor comma" href="https://huggingface.co/hassiahk">Haswanth Aekula</a>
 
9
  </div>
10
  """
11
  CHEF_INFO = """
12
+ <h2 class="font-title">Welcome to our lovely restaurant! </h2>
13
+ <p class="strong font-body">
14
  <span class="d-block extra-info">(We are at your service with two of the best chefs in the world: Chef Scheherazade,
15
+ Chef Giovanni. Scheherazade is known for being more creative whereas Giovanni is more meticulous.)</span>
16
+ </p>
17
  """.strip()
18
  PROMPT_BOX = "Add custom ingredients here (separated by `,`): "
19
  STORY = """
20
+ <div class="story-box font-body">
21
  <p>
22
  Hello everyone 👋, I am <strong>Chef Transformer</strong>,
23
  the owner of this restaurant. I was made by a group of <a href="https://huggingface.co/flax-community/t5-recipe-generation#team-members">NLP Engineers</a> to train my two prodigy recipe creators: <strong>Chef Scheherazade</strong> and <strong>Chef Giovanni</strong>.
 
30
  I trained my chefs by asking them to generate a title, a list of ingredients (including amounts!), and a list of directions after giving them just a simple list of food items.
31
  </p>
32
 
33
+ <pre>[Inputs]
 
34
  {food items*: separated by comma}
35
 
36
  [Targets]
Build Ingredients Vocab.ipynb → notes/Build Ingredients Vocab.ipynb RENAMED
File without changes
utils/__init__.py ADDED
File without changes
utils/api.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import requests
3
+
4
+
5
+ def generate_cook_image(query, app_id, app_key):
6
+ api_url = f"https://api.edamam.com/api/recipes/v2?type=public&q={query}&app_id={app_id}&app_key={app_key}&field=image"
7
+
8
+ try:
9
+ r = requests.get(api_url)
10
+ if r.status_code != 200:
11
+ return None
12
+
13
+ rj = r.json()
14
+ if "hits" not in rj or not len(rj["hits"]) > 0:
15
+ return None
16
+
17
+ data = rj["hits"]
18
+ data = data[random.randint(1, min(5, len(data) - 1))] if len(data) > 1 else data[0]
19
+
20
+ if "recipe" not in data or "image" not in data["recipe"]:
21
+ return None
22
+
23
+ image = data["recipe"]["image"]
24
+ return image
25
+ except Exception as e:
26
+ return None
utils/draw.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import (
2
+ Image,
3
+ ImageFont,
4
+ ImageDraw
5
+ )
6
+ import textwrap
7
+ from .utils import load_image_from_url
8
+ from .ext import (
9
+ ingredients as ext_ingredients,
10
+ directions as ext_directions
11
+ )
12
+
13
+
14
+ def generate_food_with_logo_image(bg_path, logo_path, food_url, no_food="asset/frame/no_food.png"):
15
+ bg = Image.open(bg_path)
16
+ width, height = bg.size
17
+
18
+ logo = Image.open(logo_path)
19
+ logo_width, logo_height, logo_ratio, logo_rb, logo_mb = logo.size + (3, -20, 45)
20
+ logo_width, logo_height = (logo_width // logo_ratio, logo_height // logo_ratio)
21
+ logo = logo.resize((logo_width, logo_height))
22
+
23
+ food = load_image_from_url(food_url, rgba_mode=True, default_image=no_food)
24
+
25
+ food_width, food_height = (300, 300)
26
+ food = food.resize((food_width, food_height))
27
+
28
+ bg.paste(food, (0, 0), food)
29
+ bg.paste(logo, (width - logo_width - logo_rb, height - logo_height - logo_mb), logo)
30
+
31
+ return bg
32
+
33
+
34
+ def generate_recipe_image(
35
+ recipe_data,
36
+ bg_path,
37
+ food_logo_ia,
38
+ fonts,
39
+ bg_color="#ffffff"
40
+ ):
41
+ bg = Image.open(bg_path)
42
+ bg.paste(food_logo_ia, (50, 50), food_logo_ia)
43
+ bg_color = Image.new("RGBA", bg.size, bg_color)
44
+ bg_color.paste(bg, mask=bg)
45
+
46
+ im_editable = ImageDraw.Draw(bg_color)
47
+ im_editable.text(
48
+ (418, 30),
49
+ textwrap.fill(recipe_data["title"], 15).replace(" \n", "\n"),
50
+ (61, 61, 70),
51
+ font=fonts["title"],
52
+ )
53
+
54
+ im_editable.text(
55
+ (100, 450),
56
+ "Ingredients",
57
+ (61, 61, 70),
58
+ font=fonts["body_bold"],
59
+ )
60
+ ingredients = recipe_data["ingredients"]
61
+ ingredients = [textwrap.fill(item, 30).replace("\n", "\n ") for item in ingredients]
62
+ ingredients = ext_ingredients(ingredients, [], without_mapping=True)
63
+
64
+ im_editable.text(
65
+ (50, 520),
66
+ "\n".join([f"- {item}" for item in ingredients]),
67
+ (61, 61, 70),
68
+ font=fonts["body"],
69
+ )
70
+
71
+ im_editable.text(
72
+ (700, 450),
73
+ "Directions",
74
+ (61, 61, 70),
75
+ font=fonts["body_bold"],
76
+ )
77
+
78
+ directions = recipe_data["directions"]
79
+ directions = [textwrap.fill(item, 70).replace("\n", "\n ").capitalize() for item in directions]
80
+ im_editable.text(
81
+ (430, 520),
82
+ "\n".join([f"{i + 1}. {item}" for i, item in enumerate(directions)]).strip(),
83
+ (61, 61, 70),
84
+ font=fonts["body"],
85
+ )
86
+ return bg_color
utils/ext.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from .utils import replace_regex
3
+
4
+ DEFAULT_MAP_DICT = {
5
+ " c ": " c. ",
6
+ ", chopped": " (chopped)",
7
+ ", crumbled": " (crumbled)",
8
+ ", thawed": " (thawed)",
9
+ ", melted": " (melted)",
10
+ }
11
+
12
+
13
+ def ingredient(text, map_dict):
14
+ if len(map_dict) > 0:
15
+ map_dict.update(**DEFAULT_MAP_DICT)
16
+ else:
17
+ map_dict = DEFAULT_MAP_DICT
18
+
19
+ text = replace_regex(text, map_dict)
20
+ text = re.sub(r"(\d)\s(\d\/\d)", r" \1+\2 ", text)
21
+ text = " ".join([word.strip() for word in text.split() if word.strip()])
22
+ return text
23
+
24
+
25
+ def ingredients(text_list, item_list, without_mapping=False):
26
+ map_dict = {
27
+ item: f'<span class="text-bold">{item}</span>' for item in list(map(lambda x: x.lower().strip(), item_list))
28
+ }
29
+ text_list = list(map(lambda x: x.lower(), text_list))
30
+
31
+ output = []
32
+ for text in text_list:
33
+ map_dict = map_dict if not without_mapping else {}
34
+ text = ingredient(text, map_dict)
35
+ output.append(text)
36
+
37
+ return output
38
+
39
+
40
+ def directions(text_list):
41
+ text_list = list(map(lambda x: x.lower().capitalize(), text_list))
42
+
43
+ return text_list
utils/st.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+
4
+ def local_css(css_path):
5
+ with open(css_path) as f:
6
+ st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
7
+
8
+
9
+ def remote_css(css_url):
10
+ st.markdown(f'<link href="{css_url}" rel="stylesheet">', unsafe_allow_html=True)
utils/utils.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import json
3
+ from io import BytesIO
4
+ from PIL import Image
5
+ import requests
6
+ import re
7
+
8
+
9
+ def load_image_from_local(image_path, image_resize=None):
10
+ image = Image.open(image_path)
11
+
12
+ if isinstance(image_resize, tuple):
13
+ image = image.resize(image_resize)
14
+ return image
15
+
16
+
17
+ def load_image_from_url(image_url, rgba_mode=False, image_resize=None, default_image=None):
18
+ try:
19
+ image = Image.open(requests.get(image_url, stream=True).raw)
20
+
21
+ if rgba_mode:
22
+ image = image.convert("RGBA")
23
+
24
+ if isinstance(image_resize, tuple):
25
+ image = image.resize(image_resize)
26
+
27
+ except Exception as e:
28
+ image = None
29
+ if default_image:
30
+ image = load_image_from_local(default_image, image_resize=image_resize)
31
+
32
+ return image
33
+
34
+
35
+ def load_text(text_path):
36
+ text = ''
37
+ with open(text_path) as f:
38
+ text = f.read()
39
+
40
+ return text
41
+
42
+
43
+ def load_json(json_path):
44
+ jdata = ''
45
+ with open(json_path) as f:
46
+ jdata = json.load(f)
47
+
48
+ return jdata
49
+
50
+
51
+ def image_to_base64(image_array):
52
+ buffered = BytesIO()
53
+ image_array.save(buffered, format="PNG")
54
+ image_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
55
+ return f"data:image/png;base64, {image_b64}"
56
+
57
+
58
+ def unique_list(seq):
59
+ seen = set()
60
+ seen_add = seen.add
61
+ return [x for x in seq if not (x in seen or seen_add(x))]
62
+
63
+
64
+ def pure_comma_separation(list_str, return_list=True):
65
+ r = unique_list([item.strip() for item in list_str.lower().split(",") if item.strip()])
66
+ if return_list:
67
+ return r
68
+ return ", ".join(r)
69
+
70
+
71
+ def replace_regex(text, map_dict):
72
+ pattern = "|".join(map(re.escape, map_dict.keys()))
73
+ return re.sub(pattern, lambda m: map_dict[m.group()], str(text))