Spaces:
Runtime error
Runtime error
Add GenFood into generated recipe
Browse files- app.py +192 -138
- asset/css/style.css +35 -3
- asset/frame/export/food-image-logo.png +0 -0
- asset/frame/food-image-logo-background.png +0 -0
- asset/frame/food-image-logo-bg-g.png +0 -0
- asset/frame/food-image-logo-bg-s.png +0 -0
- asset/frame/food.jpg +0 -0
- asset/frame/logo.png +0 -0
- asset/frame/no_food.png +0 -0
- asset/frame/recipe-bg.png +0 -0
- dummy.py +85 -0
- meta.py +28 -14
- requirements.txt +2 -1
- utils.py +31 -2
app.py
CHANGED
@@ -9,78 +9,149 @@ from PIL import (
|
|
9 |
ImageFont,
|
10 |
ImageDraw
|
11 |
)
|
|
|
12 |
|
13 |
-
import
|
14 |
-
from datetime import datetime
|
15 |
-
import glob
|
16 |
import re
|
17 |
import random
|
18 |
import textwrap
|
19 |
from examples import EXAMPLES
|
|
|
20 |
import meta
|
21 |
from utils import (
|
22 |
remote_css,
|
23 |
local_css,
|
24 |
-
|
|
|
|
|
25 |
pure_comma_separation
|
26 |
)
|
27 |
|
28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
|
30 |
|
31 |
class TextGeneration:
|
32 |
def __init__(self):
|
33 |
-
self.debug =
|
34 |
-
self.
|
35 |
-
'directions': [
|
36 |
-
"for the dough",
|
37 |
-
"in a small bowl, combine the warm water and yeast.",
|
38 |
-
"let it sit for 5 minutes.",
|
39 |
-
"add the flour, salt, and olive oil.",
|
40 |
-
"mix well and knead until the dough is smooth and elastic.",
|
41 |
-
"cover the dough with a damp towel and let it rise for about 1 hour.",
|
42 |
-
"for the filling",
|
43 |
-
"heat a large skillet over medium high heat.",
|
44 |
-
"cook the beef and onion until the beef is browned and the onion is translucent. browned and the onion is translucent.",
|
45 |
-
"drain off any excess grease.",
|
46 |
-
"stir in the pepper and salt and black pepper to taste.",
|
47 |
-
"remove from the heat and set aside.",
|
48 |
-
"preheat the oven to 425 degrees f.",
|
49 |
-
"roll out the dough on a lightly floured surface into a 12 inch circle.",
|
50 |
-
"spread the beef mixture over the dough, leaving a 1 inch border.",
|
51 |
-
"top with the feta, parsley, and lemon juice.",
|
52 |
-
"bake for 20 minutes or until the crust is golden brown.",
|
53 |
-
"cut into wedges and serve.",
|
54 |
-
],
|
55 |
-
'ingredients': [
|
56 |
-
'1 potato',
|
57 |
-
'1 slice cheese 1 slice cheese',
|
58 |
-
'1 potato 1 slice cheese 1 slice cheese',
|
59 |
-
'1 slice cheese'
|
60 |
-
'1 potato',
|
61 |
-
'1 slice cheese',
|
62 |
-
'1 slice cheese',
|
63 |
-
'1 potato',
|
64 |
-
'1 slice cheese'
|
65 |
-
'1 potato',
|
66 |
-
'1 slice cheese',
|
67 |
-
],
|
68 |
-
'title': 'Cheese Potatoes with Some other items'
|
69 |
-
}
|
70 |
self.tokenizer = None
|
71 |
self.generator = None
|
|
|
|
|
|
|
72 |
self.task = "text2text-generation"
|
73 |
self.model_name_or_path = "flax-community/t5-recipe-generation"
|
74 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
self.fonts = {
|
76 |
-
"title": ImageFont.truetype("asset/fonts/Poppins-Bold.ttf",
|
77 |
"sub_title": ImageFont.truetype("asset/fonts/Poppins-Medium.ttf", 30),
|
78 |
-
"body_bold": ImageFont.truetype("asset/fonts/Montserrat-Bold.ttf",
|
79 |
-
"body": ImageFont.truetype("asset/fonts/Montserrat-Regular.ttf",
|
80 |
|
81 |
}
|
82 |
-
self.list_division = 5
|
83 |
-
self.point = "β’"
|
84 |
set_seed(42)
|
85 |
|
86 |
def _skip_special_tokens_and_prettify(self, text):
|
@@ -109,81 +180,43 @@ class TextGeneration:
|
|
109 |
|
110 |
return data
|
111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
def load(self):
|
|
|
113 |
if not self.debug:
|
114 |
-
self.
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
)
|
126 |
-
im_editable.text(
|
127 |
-
(1570, 170 + 70),
|
128 |
-
"By " + recipe["by"],
|
129 |
-
(61, 61, 70),
|
130 |
-
font=self.fonts["sub_title"],
|
131 |
)
|
132 |
-
# Title
|
133 |
-
im_editable.text(
|
134 |
-
(400, 650),
|
135 |
-
textwrap.fill(recipe["title"], 30),
|
136 |
-
(61, 61, 70),
|
137 |
-
font=self.fonts["title"],
|
138 |
-
)
|
139 |
-
# Ingredients
|
140 |
-
im_editable.text(
|
141 |
-
(100, 1000),
|
142 |
-
"Ingredients",
|
143 |
-
(61, 61, 70),
|
144 |
-
font=self.fonts["body_bold"],
|
145 |
-
)
|
146 |
-
ingredients = recipe["ingredients"]
|
147 |
-
ingredients = [textwrap.fill(item, 30).replace("\n", "\n ") for item in ingredients]
|
148 |
-
|
149 |
-
im_editable.text(
|
150 |
-
(100, 1080),
|
151 |
-
"\n".join([f"{self.point} {item}" for item in ingredients]),
|
152 |
-
(61, 61, 70),
|
153 |
-
font=self.fonts["body"],
|
154 |
-
)
|
155 |
-
# Directions
|
156 |
-
im_editable.text(
|
157 |
-
(700, 1000),
|
158 |
-
"Directions",
|
159 |
-
(61, 61, 70),
|
160 |
-
font=self.fonts["body_bold"],
|
161 |
-
)
|
162 |
-
directions = recipe["directions"]
|
163 |
-
directions = [textwrap.fill(item, 80).replace("\n", "\n ") for item in directions]
|
164 |
-
im_editable.text(
|
165 |
-
(700, 1080),
|
166 |
-
"\n".join([f"{i + 1}. {item}" for i, item in enumerate(directions)]).strip(),
|
167 |
-
(61, 61, 70),
|
168 |
-
font=self.fonts["body"],
|
169 |
-
)
|
170 |
-
# directions_col1 = [textwrap.fill(item, 30).replace("\n", "\n ") for item in directions[:self.list_division]]
|
171 |
-
# directions_col2 = [textwrap.fill(item, 30).replace("\n", "\n ") for item in directions[self.list_division:]]
|
172 |
-
# im_editable.text(
|
173 |
-
# (700, 1130),
|
174 |
-
# "\n".join([f"{i + 1}. {item}" for i, item in enumerate(directions_col1)]).strip(),
|
175 |
-
# (61, 61, 70),
|
176 |
-
# font=self.fonts["body"],
|
177 |
-
# )
|
178 |
-
# im_editable.text(
|
179 |
-
# (1300, 1130),
|
180 |
-
# "\n".join([f"{i + 1 + self.list_division}. {item}" for i, item in enumerate(directions_col2)]).strip(),
|
181 |
-
# (61, 61, 70),
|
182 |
-
# font=self.fonts["body"],
|
183 |
-
# )
|
184 |
return frame
|
185 |
|
186 |
def generate(self, items, generation_kwargs):
|
|
|
|
|
187 |
if not self.debug:
|
188 |
generation_kwargs["num_return_sequences"] = 1
|
189 |
# generation_kwargs["return_full_text"] = False
|
@@ -196,13 +229,25 @@ class TextGeneration:
|
|
196 |
)[0]["generated_token_ids"]
|
197 |
recipe = self.tokenizer.decode(generated_ids, skip_special_tokens=False)
|
198 |
recipe = self._skip_special_tokens_and_prettify(recipe)
|
199 |
-
return recipe
|
200 |
|
201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
|
203 |
-
def generate_frame(self, recipe,
|
204 |
-
|
205 |
-
return self.prepare_frame(recipe, frame)
|
206 |
|
207 |
|
208 |
@st.cache(allow_output_mutation=True)
|
@@ -240,17 +285,17 @@ def main():
|
|
240 |
initial_sidebar_state="expanded"
|
241 |
)
|
242 |
generator = load_text_generator()
|
243 |
-
if hasattr(st, "session_state"):
|
244 |
-
|
245 |
-
|
246 |
-
else:
|
247 |
-
|
248 |
|
249 |
local_css("asset/css/style.css")
|
250 |
|
251 |
-
col1, col2 = st.beta_columns([
|
252 |
with col2:
|
253 |
-
st.image(
|
254 |
st.markdown(meta.SIDEBAR_INFO, unsafe_allow_html=True)
|
255 |
|
256 |
with st.beta_expander("Where did this story start?"):
|
@@ -288,10 +333,10 @@ def main():
|
|
288 |
unsafe_allow_html=True
|
289 |
)
|
290 |
if recipe_button:
|
291 |
-
if hasattr(st, "session_state"):
|
292 |
-
|
293 |
-
else:
|
294 |
-
|
295 |
|
296 |
entered_items.markdown("**Generate recipe for:** " + items)
|
297 |
with st.spinner("Generating recipe..."):
|
@@ -306,6 +351,9 @@ def main():
|
|
306 |
generated_recipe = generator.generate(items, gen_kw)
|
307 |
|
308 |
title = generated_recipe["title"]
|
|
|
|
|
|
|
309 |
ingredients = generated_recipe["ingredients"]
|
310 |
directions = [textwrap.fill(item, 70).replace("\n", "\n ") for item in
|
311 |
generated_recipe["directions"]]
|
@@ -315,15 +363,17 @@ def main():
|
|
315 |
|
316 |
with r1:
|
317 |
# st.write(st.session_state.get_random_frame)
|
318 |
-
if hasattr(st, "session_state"):
|
319 |
-
|
320 |
-
else:
|
321 |
-
|
|
|
|
|
322 |
|
323 |
st.image(
|
324 |
recipe_post,
|
325 |
# width=500,
|
326 |
-
caption="
|
327 |
use_column_width="auto",
|
328 |
output_format="PNG"
|
329 |
)
|
@@ -332,7 +382,11 @@ def main():
|
|
332 |
st.markdown(
|
333 |
" ".join([
|
334 |
"<div class='r-text-recipe'>",
|
|
|
|
|
335 |
f"<h2>{title}</h2>",
|
|
|
|
|
336 |
"<h3>Ingredients</h3>",
|
337 |
"<ul class='ingredients-list'>",
|
338 |
" ".join([f'<li>{item}</li>' for item in ingredients]),
|
|
|
9 |
ImageFont,
|
10 |
ImageDraw
|
11 |
)
|
12 |
+
import requests
|
13 |
|
14 |
+
import os
|
|
|
|
|
15 |
import re
|
16 |
import random
|
17 |
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,
|
27 |
pure_comma_separation
|
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(0, min(4, 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 = True
|
132 |
+
self.dummy_outputs = dummy.recipes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
self.tokenizer = None
|
134 |
self.generator = None
|
135 |
+
self.api_ids = []
|
136 |
+
self.api_keys = []
|
137 |
+
self.api_test = 2
|
138 |
self.task = "text2text-generation"
|
139 |
self.model_name_or_path = "flax-community/t5-recipe-generation"
|
140 |
+
self.color_frame = "#ffffff"
|
141 |
+
self.main_frame = "asset/frame/recipe-bg.png"
|
142 |
+
self.no_food = "asset/frame/no_food.png"
|
143 |
+
self.logo_frame = "asset/frame/logo.png"
|
144 |
+
self.chef_frames = {
|
145 |
+
"scheherazade": "asset/frame/food-image-logo-bg-s.png",
|
146 |
+
"giovanni": "asset/frame/food-image-logo-bg-g.png",
|
147 |
+
}
|
148 |
self.fonts = {
|
149 |
+
"title": ImageFont.truetype("asset/fonts/Poppins-Bold.ttf", 70),
|
150 |
"sub_title": ImageFont.truetype("asset/fonts/Poppins-Medium.ttf", 30),
|
151 |
+
"body_bold": ImageFont.truetype("asset/fonts/Montserrat-Bold.ttf", 22),
|
152 |
+
"body": ImageFont.truetype("asset/fonts/Montserrat-Regular.ttf", 18),
|
153 |
|
154 |
}
|
|
|
|
|
155 |
set_seed(42)
|
156 |
|
157 |
def _skip_special_tokens_and_prettify(self, text):
|
|
|
180 |
|
181 |
return data
|
182 |
|
183 |
+
def load_pipeline(self):
|
184 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name_or_path)
|
185 |
+
self.generator = pipeline(self.task, model=self.model_name_or_path, tokenizer=self.model_name_or_path)
|
186 |
+
|
187 |
+
def load_api(self):
|
188 |
+
app_ids = os.getenv("EDAMAM_APP_ID")
|
189 |
+
app_ids = app_ids.split(",") if app_ids else []
|
190 |
+
app_keys = os.getenv("EDAMAM_APP_KEY")
|
191 |
+
app_keys = app_keys.split(",") if app_keys else []
|
192 |
+
|
193 |
+
if len(app_ids) != len(app_keys):
|
194 |
+
self.api_ids = []
|
195 |
+
self.api_keys = []
|
196 |
+
|
197 |
+
self.api_ids = app_ids
|
198 |
+
self.api_keys = app_keys
|
199 |
+
|
200 |
def load(self):
|
201 |
+
self.load_api()
|
202 |
if not self.debug:
|
203 |
+
self.load_pipeline()
|
204 |
+
|
205 |
+
def prepare_frame(self, recipe, chef_name):
|
206 |
+
frame_path = self.chef_frames[chef_name.lower()]
|
207 |
+
food_logo = generate_food_with_logo_image(frame_path, self.logo_frame, recipe["image"])
|
208 |
+
frame = generate_recipe_image(
|
209 |
+
recipe,
|
210 |
+
self.main_frame,
|
211 |
+
food_logo,
|
212 |
+
self.fonts,
|
213 |
+
bg_color="#ffffff"
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
221 |
generation_kwargs["num_return_sequences"] = 1
|
222 |
# generation_kwargs["return_full_text"] = False
|
|
|
229 |
)[0]["generated_token_ids"]
|
230 |
recipe = self.tokenizer.decode(generated_ids, skip_special_tokens=False)
|
231 |
recipe = self._skip_special_tokens_and_prettify(recipe)
|
|
|
232 |
|
233 |
+
if self.api_ids and self.api_keys and len(self.api_ids) == len(self.api_keys):
|
234 |
+
test = 0
|
235 |
+
for i in range(len(self.api_keys)):
|
236 |
+
if test > self.api_test:
|
237 |
+
recipe["image"] = None
|
238 |
+
break
|
239 |
+
image = generate_cook_image(recipe["title"].lower(), self.api_ids[i], self.api_keys[i])
|
240 |
+
test += 1
|
241 |
+
if image:
|
242 |
+
recipe["image"] = image
|
243 |
+
break
|
244 |
+
else:
|
245 |
+
recipe["image"] = None
|
246 |
+
|
247 |
+
return recipe
|
248 |
|
249 |
+
def generate_frame(self, recipe, chef_name):
|
250 |
+
return self.prepare_frame(recipe, chef_name)
|
|
|
251 |
|
252 |
|
253 |
@st.cache(allow_output_mutation=True)
|
|
|
285 |
initial_sidebar_state="expanded"
|
286 |
)
|
287 |
generator = load_text_generator()
|
288 |
+
# if hasattr(st, "session_state"):
|
289 |
+
# if 'get_random_frame' not in st.session_state:
|
290 |
+
# st.session_state.get_random_frame = generator.frames[0]
|
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)
|
300 |
|
301 |
with st.beta_expander("Where did this story start?"):
|
|
|
333 |
unsafe_allow_html=True
|
334 |
)
|
335 |
if recipe_button:
|
336 |
+
# if hasattr(st, "session_state"):
|
337 |
+
# st.session_state.get_random_frame = generator.frames[random.randint(0, len(generator.frames)) - 1]
|
338 |
+
# else:
|
339 |
+
# get_random_frame = generator.frames[random.randint(0, len(generator.frames)) - 1]
|
340 |
|
341 |
entered_items.markdown("**Generate recipe for:** " + items)
|
342 |
with st.spinner("Generating recipe..."):
|
|
|
351 |
generated_recipe = generator.generate(items, gen_kw)
|
352 |
|
353 |
title = generated_recipe["title"]
|
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"]]
|
|
|
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)
|
368 |
+
# else:
|
369 |
+
# recipe_post = generator.generate_frame(generated_recipe, get_random_frame)
|
370 |
+
|
371 |
+
recipe_post = generator.generate_frame(generated_recipe, chef.split()[-1])
|
372 |
|
373 |
st.image(
|
374 |
recipe_post,
|
375 |
# width=500,
|
376 |
+
caption="Save image and share on your social media",
|
377 |
use_column_width="auto",
|
378 |
output_format="PNG"
|
379 |
)
|
|
|
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]),
|
asset/css/style.css
CHANGED
@@ -35,14 +35,46 @@ body {
|
|
35 |
text-decoration: underline;
|
36 |
}
|
37 |
|
|
|
|
|
|
|
|
|
|
|
38 |
.r-text-recipe {
|
39 |
padding-left: 30px;
|
40 |
margin-left: 10px;
|
41 |
border-left: 1px dashed #eee;
|
42 |
}
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
}
|
47 |
.ingredients-list {
|
48 |
columns: 2;
|
|
|
35 |
text-decoration: underline;
|
36 |
}
|
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;
|
54 |
+
}
|
55 |
+
|
56 |
+
.divider-mask {
|
57 |
+
overflow: hidden;
|
58 |
+
height: 20px;
|
59 |
+
}
|
60 |
+
|
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;
|
asset/frame/export/food-image-logo.png
ADDED
asset/frame/food-image-logo-background.png
ADDED
asset/frame/food-image-logo-bg-g.png
ADDED
asset/frame/food-image-logo-bg-s.png
ADDED
asset/frame/food.jpg
ADDED
asset/frame/logo.png
ADDED
asset/frame/no_food.png
ADDED
asset/frame/recipe-bg.png
ADDED
dummy.py
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
recipes = [
|
2 |
+
{
|
3 |
+
'directions': [
|
4 |
+
"preheat oven to 350.",
|
5 |
+
"grease a 13 x 9 x 2 inch baking pan.",
|
6 |
+
"place 1 sheet of the dough on a work surface.",
|
7 |
+
"brush with melted butter.",
|
8 |
+
"top with another sheet of dough and brush with butter. repeat with 3 more sheets of dough, brushing each sheet with butter between each layer.",
|
9 |
+
"combine walnuts and cinnamon in a small bowl.",
|
10 |
+
"sprinkle half of the walnut mixture over the dough.",
|
11 |
+
"repeat with the remaining dough and walnut mixture.",
|
12 |
+
"starting at the long side, roll up the dough into a log.",
|
13 |
+
"cut the log into 1 inch slices.",
|
14 |
+
"arrange the slices, cut side down, in the prepared baking dish.",
|
15 |
+
"bake until golden brown, about 30 minutes.",
|
16 |
+
"meanwhile, bring water and honey to a boil.",
|
17 |
+
"remove from heat and stir in chocolate until melted.",
|
18 |
+
"pour over the cooled strudel.",
|
19 |
+
"serve warm or at room temperature.",
|
20 |
+
"makes 12 servings.",
|
21 |
+
],
|
22 |
+
'ingredients': [
|
23 |
+
"1 lb. phyllo dough, thawed",
|
24 |
+
"1 c. unsalted butter, melted",
|
25 |
+
"2 c chopped walnuts",
|
26 |
+
"1/2 tsp. cinnamon",
|
27 |
+
"1 1/2 c water",
|
28 |
+
"3/4 c honey",
|
29 |
+
"1/4 c melted chocolate",
|
30 |
+
],
|
31 |
+
'title': 'Baklava'
|
32 |
+
},
|
33 |
+
{
|
34 |
+
'directions': [
|
35 |
+
"in a large skillet, heat oil to 375.",
|
36 |
+
"season chops on both sides with salt and pepper.",
|
37 |
+
"dredge chops in flour, shaking off excess.",
|
38 |
+
"dip chops into eggs, then coat with breadcrumbs.",
|
39 |
+
"fry chops until golden brown, about 3 minutes per side.",
|
40 |
+
"transfer to a platter and keep warm.",
|
41 |
+
"pour off all but 1 tablespoon of fat from skillet.",
|
42 |
+
"add gravy and cook over medium heat, stirring occasionally, until thickened, about 5 minutes.",
|
43 |
+
"spoon gravy over chops and sprinkle with parsley.",
|
44 |
+
"makes 4 servings.",
|
45 |
+
],
|
46 |
+
'ingredients': [
|
47 |
+
"1 lb beef, cubed",
|
48 |
+
"2 tablespoons oil",
|
49 |
+
"1 large onion, chopped",
|
50 |
+
"2 medium tomatoes, peeled and chopped",
|
51 |
+
"1 teaspoon turmeric powder",
|
52 |
+
"2 limes, juice of",
|
53 |
+
"1 cup water",
|
54 |
+
"salt",
|
55 |
+
"pepper",
|
56 |
+
"1 15 ounce can red beans, drained and rinsed",
|
57 |
+
"1 tablespoon dried herb",
|
58 |
+
],
|
59 |
+
'title': 'Beef And Red Beans'
|
60 |
+
},
|
61 |
+
{
|
62 |
+
'directions': [
|
63 |
+
"heat the oil in a large saucepan.",
|
64 |
+
"add the onion and saute until golden brown.",
|
65 |
+
"stir in the tomatoes, turmeric powder, lime juice, water, salt and pepper.",
|
66 |
+
"bring to a boil.",
|
67 |
+
"reduce the heat and simmer for 10 minutes.",
|
68 |
+
"mix in the beef and beans.",
|
69 |
+
"cover and cook over low heat for 30 minutes or until the beef is tender.",
|
70 |
+
"garnish with the dried herbs.",
|
71 |
+
],
|
72 |
+
'ingredients': [
|
73 |
+
"oil for frying",
|
74 |
+
"4 6 oz. boneless pork loin chops",
|
75 |
+
"salt",
|
76 |
+
"pepper",
|
77 |
+
"1/2 c. flour",
|
78 |
+
"2 eggs, lightly beaten",
|
79 |
+
"2 tbsp. dry breadcrumbs",
|
80 |
+
"gravy",
|
81 |
+
"chopped parsley",
|
82 |
+
],
|
83 |
+
'title': 'Breaded Pork Chops With Gravy'
|
84 |
+
}
|
85 |
+
]
|
meta.py
CHANGED
@@ -15,26 +15,40 @@ Chef Giovanni. Scheherazade is known for being more creative whereas Giovanni is
|
|
15 |
""".strip()
|
16 |
PROMPT_BOX = "Add custom ingredients here (separated by `,`): "
|
17 |
STORY = """
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
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.
|
23 |
-
|
|
|
|
|
24 |
[Input]
|
25 |
{food items*: separated by comma}
|
26 |
|
27 |
[Targets]
|
28 |
-
title: {TITLE} <section>
|
29 |
-
ingredients: {INGREDIENTS: separated by <sep>} <section>
|
30 |
directions: {DIRECTIONS: separated by <sep>}.
|
31 |
-
|
32 |
-
|
33 |
-
*In the cookbooks (a.k.a [dataset](https://huggingface.co/datasets/recipe_nlg)), the food items were referred to as
|
34 |
-
NER.*
|
35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
|
37 |
-
|
38 |
-
https://huggingface.co/flax-community/t5-recipe-generation#evaluation) π― in a standardized industry test and established this restaurant π². Please tell your friends and family about us! We create each recipe with a smile on our faces π€
|
39 |
-
Everyone at the restaurant is grateful for the generous support of Hugging Face and Google for hosting Flax Community week.
|
40 |
""".strip()
|
|
|
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>.
|
22 |
+
Both of my students participated in my rigorous culinary program, <a href="https://huggingface.co/flax-community/t5-recipe-generation">T5 fine-tune training</a>,
|
23 |
+
to learn how to prepare exquisite cuisines from a wide variety of ingredients.
|
24 |
+
I've never been more proud of my students -- both can produce exceptional dishes but I regard Scheherazade as being <em>creative</em> while Giovanni is <em>meticulous</em>.
|
25 |
+
If you give each of them the same ingredients, they'll usually come up with something different. <br /><br />
|
26 |
+
At the start of the program the chefs read cookbooks containing thousands of recipes of varying difficulties and from cultures all over the world.
|
27 |
+
The NLP engineers helped guide the learning process so that the chefs could actually learn which ingredients work well together rather than just memorize recipes.
|
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]
|
36 |
+
title: {TITLE} <section>
|
37 |
+
ingredients: {INGREDIENTS: separated by <sep>} <section>
|
38 |
directions: {DIRECTIONS: separated by <sep>}.
|
39 |
+
</pre>
|
|
|
|
|
|
|
40 |
|
41 |
+
<p>
|
42 |
+
<em>In the cookbooks (a.k.a <a href="https://huggingface.co/datasets/recipe_nlg">dataset</a>), the food items were referred to as NER. </em>
|
43 |
+
</p>
|
44 |
+
<p>
|
45 |
+
In the span of a week, my chefs went from spitting out nonsense to creating masterpieces.
|
46 |
+
Their learning rate was exceptionally high and each batch of recipes was better than the last. <br />
|
47 |
+
In their final exam, they achieved <a href="https://huggingface.co/flax-community/t5-recipe-generation#evaluation">high scores</a> π― in a
|
48 |
+
standardized industry test and established this restaurant π². Please tell your friends and family about us!
|
49 |
+
We create each recipe with a smile on our faces π€ Everyone at the restaurant is grateful for the generous support of
|
50 |
+
HuggingFace and Google for hosting Flax Community week.
|
51 |
+
</p>
|
52 |
|
53 |
+
</div>
|
|
|
|
|
54 |
""".strip()
|
requirements.txt
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
streamlit==0.84.1
|
2 |
transformers
|
3 |
torch
|
4 |
-
Pillow
|
|
|
|
1 |
streamlit==0.84.1
|
2 |
transformers
|
3 |
torch
|
4 |
+
Pillow
|
5 |
+
requests
|
utils.py
CHANGED
@@ -1,15 +1,44 @@
|
|
1 |
import streamlit as st
|
2 |
import json
|
3 |
from PIL import Image
|
|
|
|
|
|
|
4 |
|
5 |
|
6 |
-
def
|
7 |
image = Image.open(image_path)
|
|
|
8 |
if isinstance(image_resize, tuple):
|
9 |
-
image.resize(image_resize)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
return image
|
11 |
|
12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
def load_text(text_path):
|
14 |
text = ''
|
15 |
with open(text_path) as f:
|
|
|
1 |
import streamlit as st
|
2 |
import json
|
3 |
from PIL import Image
|
4 |
+
from io import BytesIO
|
5 |
+
import base64
|
6 |
+
import requests
|
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 image_to_base64(image_array):
|
36 |
+
buffered = BytesIO()
|
37 |
+
image_array.save(buffered, format="PNG")
|
38 |
+
image_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
39 |
+
return f"data:image/png;base64, {image_b64}"
|
40 |
+
|
41 |
+
|
42 |
def load_text(text_path):
|
43 |
text = ''
|
44 |
with open(text_path) as f:
|