Spaces:
Build error
Build error
Duplicate from flax-community/chef-transformer
Browse filesCo-authored-by: Julien Chaumond <[email protected]>
- .gitattributes +16 -0
- .gitignore +887 -0
- CONTRIBUTING.md +35 -0
- README.md +248 -0
- app.py +320 -0
- asset/css/style.css +132 -0
- asset/fonts/Montserrat-Bold.ttf +0 -0
- asset/fonts/Montserrat-Regular.ttf +0 -0
- asset/fonts/Poppins-Bold.ttf +0 -0
- asset/fonts/Poppins-Medium.ttf +0 -0
- 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
- asset/images/chef-transformer-transparent.png +0 -0
- asset/images/chef-transformer.png +0 -0
- asset/images/frames/1.jpg +0 -0
- asset/images/frames/10.jpg +0 -0
- asset/images/frames/2.jpg +0 -0
- asset/images/frames/3.jpg +0 -0
- asset/images/frames/4.jpg +0 -0
- asset/images/frames/5.jpg +0 -0
- asset/images/frames/6.jpg +0 -0
- asset/images/frames/7.jpg +0 -0
- asset/images/frames/8.jpg +0 -0
- asset/images/frames/9.jpg +0 -0
- asset/images/logo.png +0 -0
- asset/images/recipe-frame.png +0 -0
- asset/images/recipe-post.png +0 -0
- dummy.py +85 -0
- examples.py +10 -0
- meta.py +55 -0
- notes/Build Ingredients Vocab.ipynb +213 -0
- requirements.txt +5 -0
- utils.py +78 -0
- utils/__init__.py +0 -0
- utils/api.py +26 -0
- utils/draw.py +94 -0
- utils/ext.py +44 -0
- utils/st.py +10 -0
- utils/utils.py +73 -0
.gitattributes
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.bin.* filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.tar.gz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,887 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
### TeX template
|
2 |
+
## Core latex/pdflatex auxiliary files:
|
3 |
+
*.aux
|
4 |
+
*.lof
|
5 |
+
*.log
|
6 |
+
*.lot
|
7 |
+
*.fls
|
8 |
+
*.out
|
9 |
+
*.toc
|
10 |
+
*.fmt
|
11 |
+
*.fot
|
12 |
+
*.cb
|
13 |
+
*.cb2
|
14 |
+
.*.lb
|
15 |
+
|
16 |
+
## Intermediate documents:
|
17 |
+
*.dvi
|
18 |
+
*.xdv
|
19 |
+
*-converted-to.*
|
20 |
+
# these rules might exclude image files for figures etc.
|
21 |
+
# *.ps
|
22 |
+
# *.eps
|
23 |
+
# *.pdf
|
24 |
+
|
25 |
+
## Generated if empty string is given at "Please type another file name for output:"
|
26 |
+
.pdf
|
27 |
+
|
28 |
+
## Bibliography auxiliary files (bibtex/biblatex/biber):
|
29 |
+
*.bbl
|
30 |
+
*.bcf
|
31 |
+
*.blg
|
32 |
+
*-blx.aux
|
33 |
+
*-blx.bib
|
34 |
+
*.run.xml
|
35 |
+
|
36 |
+
## Build tool auxiliary files:
|
37 |
+
*.fdb_latexmk
|
38 |
+
*.synctex
|
39 |
+
*.synctex(busy)
|
40 |
+
*.synctex.gz
|
41 |
+
*.synctex.gz(busy)
|
42 |
+
*.pdfsync
|
43 |
+
|
44 |
+
## Build tool directories for auxiliary files
|
45 |
+
# latexrun
|
46 |
+
latex.out/
|
47 |
+
|
48 |
+
## Auxiliary and intermediate files from other packages:
|
49 |
+
# algorithms
|
50 |
+
*.alg
|
51 |
+
*.loa
|
52 |
+
|
53 |
+
# achemso
|
54 |
+
acs-*.bib
|
55 |
+
|
56 |
+
# amsthm
|
57 |
+
*.thm
|
58 |
+
|
59 |
+
# beamer
|
60 |
+
*.nav
|
61 |
+
*.pre
|
62 |
+
*.snm
|
63 |
+
*.vrb
|
64 |
+
|
65 |
+
# changes
|
66 |
+
*.soc
|
67 |
+
|
68 |
+
# comment
|
69 |
+
*.cut
|
70 |
+
|
71 |
+
# cprotect
|
72 |
+
*.cpt
|
73 |
+
|
74 |
+
# elsarticle (documentclass of Elsevier journals)
|
75 |
+
*.spl
|
76 |
+
|
77 |
+
# endnotes
|
78 |
+
*.ent
|
79 |
+
|
80 |
+
# fixme
|
81 |
+
*.lox
|
82 |
+
|
83 |
+
# feynmf/feynmp
|
84 |
+
*.mf
|
85 |
+
*.mp
|
86 |
+
*.t[1-9]
|
87 |
+
*.t[1-9][0-9]
|
88 |
+
*.tfm
|
89 |
+
|
90 |
+
#(r)(e)ledmac/(r)(e)ledpar
|
91 |
+
*.end
|
92 |
+
*.?end
|
93 |
+
*.[1-9]
|
94 |
+
*.[1-9][0-9]
|
95 |
+
*.[1-9][0-9][0-9]
|
96 |
+
*.[1-9]R
|
97 |
+
*.[1-9][0-9]R
|
98 |
+
*.[1-9][0-9][0-9]R
|
99 |
+
*.eledsec[1-9]
|
100 |
+
*.eledsec[1-9]R
|
101 |
+
*.eledsec[1-9][0-9]
|
102 |
+
*.eledsec[1-9][0-9]R
|
103 |
+
*.eledsec[1-9][0-9][0-9]
|
104 |
+
*.eledsec[1-9][0-9][0-9]R
|
105 |
+
|
106 |
+
# glossaries
|
107 |
+
*.acn
|
108 |
+
*.acr
|
109 |
+
*.glg
|
110 |
+
*.glo
|
111 |
+
*.gls
|
112 |
+
*.glsdefs
|
113 |
+
*.lzo
|
114 |
+
*.lzs
|
115 |
+
|
116 |
+
# uncomment this for glossaries-extra (will ignore makeindex's style files!)
|
117 |
+
# *.ist
|
118 |
+
|
119 |
+
# gnuplottex
|
120 |
+
*-gnuplottex-*
|
121 |
+
|
122 |
+
# gregoriotex
|
123 |
+
*.gaux
|
124 |
+
*.gtex
|
125 |
+
|
126 |
+
# htlatex
|
127 |
+
*.4ct
|
128 |
+
*.4tc
|
129 |
+
*.idv
|
130 |
+
*.lg
|
131 |
+
*.trc
|
132 |
+
*.xref
|
133 |
+
|
134 |
+
# hyperref
|
135 |
+
*.brf
|
136 |
+
|
137 |
+
# knitr
|
138 |
+
*-concordance.tex
|
139 |
+
# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files
|
140 |
+
# *.tikz
|
141 |
+
*-tikzDictionary
|
142 |
+
|
143 |
+
# listings
|
144 |
+
*.lol
|
145 |
+
|
146 |
+
# luatexja-ruby
|
147 |
+
*.ltjruby
|
148 |
+
|
149 |
+
# makeidx
|
150 |
+
*.idx
|
151 |
+
*.ilg
|
152 |
+
*.ind
|
153 |
+
|
154 |
+
# minitoc
|
155 |
+
*.maf
|
156 |
+
*.mlf
|
157 |
+
*.mlt
|
158 |
+
*.mtc[0-9]*
|
159 |
+
*.slf[0-9]*
|
160 |
+
*.slt[0-9]*
|
161 |
+
*.stc[0-9]*
|
162 |
+
|
163 |
+
# minted
|
164 |
+
_minted*
|
165 |
+
*.pyg
|
166 |
+
|
167 |
+
# morewrites
|
168 |
+
*.mw
|
169 |
+
|
170 |
+
# nomencl
|
171 |
+
*.nlg
|
172 |
+
*.nlo
|
173 |
+
*.nls
|
174 |
+
|
175 |
+
# pax
|
176 |
+
*.pax
|
177 |
+
|
178 |
+
# pdfpcnotes
|
179 |
+
*.pdfpc
|
180 |
+
|
181 |
+
# sagetex
|
182 |
+
*.sagetex.sage
|
183 |
+
*.sagetex.py
|
184 |
+
*.sagetex.scmd
|
185 |
+
|
186 |
+
# scrwfile
|
187 |
+
*.wrt
|
188 |
+
|
189 |
+
# sympy
|
190 |
+
*.sout
|
191 |
+
*.sympy
|
192 |
+
sympy-plots-for-*.tex/
|
193 |
+
|
194 |
+
# pdfcomment
|
195 |
+
*.upa
|
196 |
+
*.upb
|
197 |
+
|
198 |
+
# pythontex
|
199 |
+
*.pytxcode
|
200 |
+
pythontex-files-*/
|
201 |
+
|
202 |
+
# tcolorbox
|
203 |
+
*.listing
|
204 |
+
|
205 |
+
# thmtools
|
206 |
+
*.loe
|
207 |
+
|
208 |
+
# TikZ & PGF
|
209 |
+
*.dpth
|
210 |
+
*.md5
|
211 |
+
*.auxlock
|
212 |
+
|
213 |
+
# todonotes
|
214 |
+
*.tdo
|
215 |
+
|
216 |
+
# vhistory
|
217 |
+
*.hst
|
218 |
+
*.ver
|
219 |
+
|
220 |
+
# easy-todo
|
221 |
+
*.lod
|
222 |
+
|
223 |
+
# xcolor
|
224 |
+
*.xcp
|
225 |
+
|
226 |
+
# xmpincl
|
227 |
+
*.xmpi
|
228 |
+
|
229 |
+
# xindy
|
230 |
+
*.xdy
|
231 |
+
|
232 |
+
# xypic precompiled matrices and outlines
|
233 |
+
*.xyc
|
234 |
+
*.xyd
|
235 |
+
|
236 |
+
# endfloat
|
237 |
+
*.ttt
|
238 |
+
*.fff
|
239 |
+
|
240 |
+
# Latexian
|
241 |
+
TSWLatexianTemp*
|
242 |
+
|
243 |
+
## Editors:
|
244 |
+
# WinEdt
|
245 |
+
*.bak
|
246 |
+
*.sav
|
247 |
+
|
248 |
+
# Texpad
|
249 |
+
.texpadtmp
|
250 |
+
|
251 |
+
# LyX
|
252 |
+
*.lyx~
|
253 |
+
|
254 |
+
# Kile
|
255 |
+
*.backup
|
256 |
+
|
257 |
+
# gummi
|
258 |
+
.*.swp
|
259 |
+
|
260 |
+
# KBibTeX
|
261 |
+
*~[0-9]*
|
262 |
+
|
263 |
+
# TeXnicCenter
|
264 |
+
*.tps
|
265 |
+
|
266 |
+
# auto folder when using emacs and auctex
|
267 |
+
./auto/*
|
268 |
+
*.el
|
269 |
+
|
270 |
+
# expex forward references with \gathertags
|
271 |
+
*-tags.tex
|
272 |
+
|
273 |
+
# standalone packages
|
274 |
+
*.sta
|
275 |
+
|
276 |
+
# Makeindex log files
|
277 |
+
*.lpz
|
278 |
+
|
279 |
+
# xwatermark package
|
280 |
+
*.xwm
|
281 |
+
|
282 |
+
# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib
|
283 |
+
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
|
284 |
+
# Uncomment the next line to have this generated file ignored.
|
285 |
+
#*Notes.bib
|
286 |
+
|
287 |
+
### VisualStudio template
|
288 |
+
## Ignore Visual Studio temporary files, build results, and
|
289 |
+
## files generated by popular Visual Studio add-ons.
|
290 |
+
##
|
291 |
+
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
292 |
+
|
293 |
+
# User-specific files
|
294 |
+
*.rsuser
|
295 |
+
*.suo
|
296 |
+
*.user
|
297 |
+
*.userosscache
|
298 |
+
*.sln.docstates
|
299 |
+
|
300 |
+
# User-specific files (MonoDevelop/Xamarin Studio)
|
301 |
+
*.userprefs
|
302 |
+
|
303 |
+
# Mono auto generated files
|
304 |
+
mono_crash.*
|
305 |
+
|
306 |
+
# Build results
|
307 |
+
[Dd]ebug/
|
308 |
+
[Dd]ebugPublic/
|
309 |
+
[Rr]elease/
|
310 |
+
[Rr]eleases/
|
311 |
+
x64/
|
312 |
+
x86/
|
313 |
+
[Ww][Ii][Nn]32/
|
314 |
+
[Aa][Rr][Mm]/
|
315 |
+
[Aa][Rr][Mm]64/
|
316 |
+
bld/
|
317 |
+
[Bb]in/
|
318 |
+
[Oo]bj/
|
319 |
+
[Ll]og/
|
320 |
+
[Ll]ogs/
|
321 |
+
|
322 |
+
# Visual Studio 2015/2017 cache/options directory
|
323 |
+
.vs/
|
324 |
+
# Uncomment if you have tasks that create the project's static files in wwwroot
|
325 |
+
#wwwroot/
|
326 |
+
|
327 |
+
# Visual Studio 2017 auto generated files
|
328 |
+
Generated\ Files/
|
329 |
+
|
330 |
+
# MSTest test Results
|
331 |
+
[Tt]est[Rr]esult*/
|
332 |
+
[Bb]uild[Ll]og.*
|
333 |
+
|
334 |
+
# NUnit
|
335 |
+
*.VisualState.xml
|
336 |
+
TestResult.xml
|
337 |
+
nunit-*.xml
|
338 |
+
|
339 |
+
# Build Results of an ATL Project
|
340 |
+
[Dd]ebugPS/
|
341 |
+
[Rr]eleasePS/
|
342 |
+
dlldata.c
|
343 |
+
|
344 |
+
# Benchmark Results
|
345 |
+
BenchmarkDotNet.Artifacts/
|
346 |
+
|
347 |
+
# .NET Core
|
348 |
+
project.lock.json
|
349 |
+
project.fragment.lock.json
|
350 |
+
artifacts/
|
351 |
+
|
352 |
+
# ASP.NET Scaffolding
|
353 |
+
ScaffoldingReadMe.txt
|
354 |
+
|
355 |
+
# StyleCop
|
356 |
+
StyleCopReport.xml
|
357 |
+
|
358 |
+
# Files built by Visual Studio
|
359 |
+
*_i.c
|
360 |
+
*_p.c
|
361 |
+
*_h.h
|
362 |
+
*.ilk
|
363 |
+
*.meta
|
364 |
+
*.obj
|
365 |
+
*.iobj
|
366 |
+
*.pch
|
367 |
+
*.pdb
|
368 |
+
*.ipdb
|
369 |
+
*.pgc
|
370 |
+
*.pgd
|
371 |
+
*.rsp
|
372 |
+
*.sbr
|
373 |
+
*.tlb
|
374 |
+
*.tli
|
375 |
+
*.tlh
|
376 |
+
*.tmp
|
377 |
+
*.tmp_proj
|
378 |
+
*_wpftmp.csproj
|
379 |
+
*.log
|
380 |
+
*.vspscc
|
381 |
+
*.vssscc
|
382 |
+
.builds
|
383 |
+
*.pidb
|
384 |
+
*.svclog
|
385 |
+
*.scc
|
386 |
+
|
387 |
+
# Chutzpah Test files
|
388 |
+
_Chutzpah*
|
389 |
+
|
390 |
+
# Visual C++ cache files
|
391 |
+
ipch/
|
392 |
+
*.aps
|
393 |
+
*.ncb
|
394 |
+
*.opendb
|
395 |
+
*.opensdf
|
396 |
+
*.sdf
|
397 |
+
*.cachefile
|
398 |
+
*.VC.db
|
399 |
+
*.VC.VC.opendb
|
400 |
+
|
401 |
+
# Visual Studio profiler
|
402 |
+
*.psess
|
403 |
+
*.vsp
|
404 |
+
*.vspx
|
405 |
+
*.sap
|
406 |
+
|
407 |
+
# Visual Studio Trace Files
|
408 |
+
*.e2e
|
409 |
+
|
410 |
+
# TFS 2012 Local Workspace
|
411 |
+
$tf/
|
412 |
+
|
413 |
+
# Guidance Automation Toolkit
|
414 |
+
*.gpState
|
415 |
+
|
416 |
+
# ReSharper is a .NET coding add-in
|
417 |
+
_ReSharper*/
|
418 |
+
*.[Rr]e[Ss]harper
|
419 |
+
*.DotSettings.user
|
420 |
+
|
421 |
+
# TeamCity is a build add-in
|
422 |
+
_TeamCity*
|
423 |
+
|
424 |
+
# DotCover is a Code Coverage Tool
|
425 |
+
*.dotCover
|
426 |
+
|
427 |
+
# AxoCover is a Code Coverage Tool
|
428 |
+
.axoCover/*
|
429 |
+
!.axoCover/settings.json
|
430 |
+
|
431 |
+
# Coverlet is a free, cross platform Code Coverage Tool
|
432 |
+
coverage*.json
|
433 |
+
coverage*.xml
|
434 |
+
coverage*.info
|
435 |
+
|
436 |
+
# Visual Studio code coverage results
|
437 |
+
*.coverage
|
438 |
+
*.coveragexml
|
439 |
+
|
440 |
+
# NCrunch
|
441 |
+
_NCrunch_*
|
442 |
+
.*crunch*.local.xml
|
443 |
+
nCrunchTemp_*
|
444 |
+
|
445 |
+
# MightyMoose
|
446 |
+
*.mm.*
|
447 |
+
AutoTest.Net/
|
448 |
+
|
449 |
+
# Web workbench (sass)
|
450 |
+
.sass-cache/
|
451 |
+
|
452 |
+
# Installshield output folder
|
453 |
+
[Ee]xpress/
|
454 |
+
|
455 |
+
# DocProject is a documentation generator add-in
|
456 |
+
DocProject/buildhelp/
|
457 |
+
DocProject/Help/*.HxT
|
458 |
+
DocProject/Help/*.HxC
|
459 |
+
DocProject/Help/*.hhc
|
460 |
+
DocProject/Help/*.hhk
|
461 |
+
DocProject/Help/*.hhp
|
462 |
+
DocProject/Help/Html2
|
463 |
+
DocProject/Help/html
|
464 |
+
|
465 |
+
# Click-Once directory
|
466 |
+
publish/
|
467 |
+
|
468 |
+
# Publish Web Output
|
469 |
+
*.[Pp]ublish.xml
|
470 |
+
*.azurePubxml
|
471 |
+
# Note: Comment the next line if you want to checkin your web deploy settings,
|
472 |
+
# but database connection strings (with potential passwords) will be unencrypted
|
473 |
+
*.pubxml
|
474 |
+
*.publishproj
|
475 |
+
|
476 |
+
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
477 |
+
# checkin your Azure Web App publish settings, but sensitive information contained
|
478 |
+
# in these scripts will be unencrypted
|
479 |
+
PublishScripts/
|
480 |
+
|
481 |
+
# NuGet Packages
|
482 |
+
*.nupkg
|
483 |
+
# NuGet Symbol Packages
|
484 |
+
*.snupkg
|
485 |
+
# The packages folder can be ignored because of Package Restore
|
486 |
+
**/[Pp]ackages/*
|
487 |
+
# except build/, which is used as an MSBuild target.
|
488 |
+
!**/[Pp]ackages/build/
|
489 |
+
# Uncomment if necessary however generally it will be regenerated when needed
|
490 |
+
#!**/[Pp]ackages/repositories.config
|
491 |
+
# NuGet v3's project.json files produces more ignorable files
|
492 |
+
*.nuget.props
|
493 |
+
*.nuget.targets
|
494 |
+
|
495 |
+
# Microsoft Azure Build Output
|
496 |
+
csx/
|
497 |
+
*.build.csdef
|
498 |
+
|
499 |
+
# Microsoft Azure Emulator
|
500 |
+
ecf/
|
501 |
+
rcf/
|
502 |
+
|
503 |
+
# Windows Store app package directories and files
|
504 |
+
AppPackages/
|
505 |
+
BundleArtifacts/
|
506 |
+
Package.StoreAssociation.xml
|
507 |
+
_pkginfo.txt
|
508 |
+
*.appx
|
509 |
+
*.appxbundle
|
510 |
+
*.appxupload
|
511 |
+
|
512 |
+
# Visual Studio cache files
|
513 |
+
# files ending in .cache can be ignored
|
514 |
+
*.[Cc]ache
|
515 |
+
# but keep track of directories ending in .cache
|
516 |
+
!?*.[Cc]ache/
|
517 |
+
|
518 |
+
# Others
|
519 |
+
ClientBin/
|
520 |
+
~$*
|
521 |
+
*~
|
522 |
+
*.dbmdl
|
523 |
+
*.dbproj.schemaview
|
524 |
+
*.jfm
|
525 |
+
*.pfx
|
526 |
+
*.publishsettings
|
527 |
+
orleans.codegen.cs
|
528 |
+
|
529 |
+
# Including strong name files can present a security risk
|
530 |
+
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
531 |
+
#*.snk
|
532 |
+
|
533 |
+
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
534 |
+
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
535 |
+
#bower_components/
|
536 |
+
|
537 |
+
# RIA/Silverlight projects
|
538 |
+
Generated_Code/
|
539 |
+
|
540 |
+
# Backup & report files from converting an old project file
|
541 |
+
# to a newer Visual Studio version. Backup files are not needed,
|
542 |
+
# because we have git ;-)
|
543 |
+
_UpgradeReport_Files/
|
544 |
+
Backup*/
|
545 |
+
UpgradeLog*.XML
|
546 |
+
UpgradeLog*.htm
|
547 |
+
ServiceFabricBackup/
|
548 |
+
*.rptproj.bak
|
549 |
+
|
550 |
+
# SQL Server files
|
551 |
+
*.mdf
|
552 |
+
*.ldf
|
553 |
+
*.ndf
|
554 |
+
|
555 |
+
# Business Intelligence projects
|
556 |
+
*.rdl.data
|
557 |
+
*.bim.layout
|
558 |
+
*.bim_*.settings
|
559 |
+
*.rptproj.rsuser
|
560 |
+
*- [Bb]ackup.rdl
|
561 |
+
*- [Bb]ackup ([0-9]).rdl
|
562 |
+
*- [Bb]ackup ([0-9][0-9]).rdl
|
563 |
+
|
564 |
+
# Microsoft Fakes
|
565 |
+
FakesAssemblies/
|
566 |
+
|
567 |
+
# GhostDoc plugin setting file
|
568 |
+
*.GhostDoc.xml
|
569 |
+
|
570 |
+
# Node.js Tools for Visual Studio
|
571 |
+
.ntvs_analysis.dat
|
572 |
+
node_modules/
|
573 |
+
|
574 |
+
# Visual Studio 6 build log
|
575 |
+
*.plg
|
576 |
+
|
577 |
+
# Visual Studio 6 workspace options file
|
578 |
+
*.opt
|
579 |
+
|
580 |
+
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
581 |
+
*.vbw
|
582 |
+
|
583 |
+
# Visual Studio LightSwitch build output
|
584 |
+
**/*.HTMLClient/GeneratedArtifacts
|
585 |
+
**/*.DesktopClient/GeneratedArtifacts
|
586 |
+
**/*.DesktopClient/ModelManifest.xml
|
587 |
+
**/*.Server/GeneratedArtifacts
|
588 |
+
**/*.Server/ModelManifest.xml
|
589 |
+
_Pvt_Extensions
|
590 |
+
|
591 |
+
# Paket dependency manager
|
592 |
+
.paket/paket.exe
|
593 |
+
paket-files/
|
594 |
+
|
595 |
+
# FAKE - F# Make
|
596 |
+
.fake/
|
597 |
+
|
598 |
+
# CodeRush personal settings
|
599 |
+
.cr/personal
|
600 |
+
|
601 |
+
# Python Tools for Visual Studio (PTVS)
|
602 |
+
__pycache__/
|
603 |
+
*.pyc
|
604 |
+
|
605 |
+
# Cake - Uncomment if you are using it
|
606 |
+
# tools/**
|
607 |
+
# !tools/packages.config
|
608 |
+
|
609 |
+
# Tabs Studio
|
610 |
+
*.tss
|
611 |
+
|
612 |
+
# Telerik's JustMock configuration file
|
613 |
+
*.jmconfig
|
614 |
+
|
615 |
+
# BizTalk build output
|
616 |
+
*.btp.cs
|
617 |
+
*.btm.cs
|
618 |
+
*.odx.cs
|
619 |
+
*.xsd.cs
|
620 |
+
|
621 |
+
# OpenCover UI analysis results
|
622 |
+
OpenCover/
|
623 |
+
|
624 |
+
# Azure Stream Analytics local run output
|
625 |
+
ASALocalRun/
|
626 |
+
|
627 |
+
# MSBuild Binary and Structured Log
|
628 |
+
*.binlog
|
629 |
+
|
630 |
+
# NVidia Nsight GPU debugger configuration file
|
631 |
+
*.nvuser
|
632 |
+
|
633 |
+
# MFractors (Xamarin productivity tool) working folder
|
634 |
+
.mfractor/
|
635 |
+
|
636 |
+
# Local History for Visual Studio
|
637 |
+
.localhistory/
|
638 |
+
|
639 |
+
# BeatPulse healthcheck temp database
|
640 |
+
healthchecksdb
|
641 |
+
|
642 |
+
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
643 |
+
MigrationBackup/
|
644 |
+
|
645 |
+
# Ionide (cross platform F# VS Code tools) working folder
|
646 |
+
.ionide/
|
647 |
+
|
648 |
+
# Fody - auto-generated XML schema
|
649 |
+
FodyWeavers.xsd
|
650 |
+
|
651 |
+
### Python template
|
652 |
+
# Byte-compiled / optimized / DLL files
|
653 |
+
__pycache__/
|
654 |
+
*.py[cod]
|
655 |
+
*$py.class
|
656 |
+
|
657 |
+
# C extensions
|
658 |
+
*.so
|
659 |
+
|
660 |
+
# Distribution / packaging
|
661 |
+
.Python
|
662 |
+
build/
|
663 |
+
develop-eggs/
|
664 |
+
dist/
|
665 |
+
downloads/
|
666 |
+
eggs/
|
667 |
+
.eggs/
|
668 |
+
lib/
|
669 |
+
lib64/
|
670 |
+
parts/
|
671 |
+
sdist/
|
672 |
+
var/
|
673 |
+
wheels/
|
674 |
+
share/python-wheels/
|
675 |
+
*.egg-info/
|
676 |
+
.installed.cfg
|
677 |
+
*.egg
|
678 |
+
MANIFEST
|
679 |
+
|
680 |
+
# PyInstaller
|
681 |
+
# Usually these files are written by a python script from a template
|
682 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
683 |
+
*.manifest
|
684 |
+
*.spec
|
685 |
+
|
686 |
+
# Installer logs
|
687 |
+
pip-log.txt
|
688 |
+
pip-delete-this-directory.txt
|
689 |
+
|
690 |
+
# Unit test / coverage reports
|
691 |
+
htmlcov/
|
692 |
+
.tox/
|
693 |
+
.nox/
|
694 |
+
.coverage
|
695 |
+
.coverage.*
|
696 |
+
.cache
|
697 |
+
nosetests.xml
|
698 |
+
coverage.xml
|
699 |
+
*.cover
|
700 |
+
*.py,cover
|
701 |
+
.hypothesis/
|
702 |
+
.pytest_cache/
|
703 |
+
cover/
|
704 |
+
|
705 |
+
# Translations
|
706 |
+
*.mo
|
707 |
+
*.pot
|
708 |
+
|
709 |
+
# Django stuff:
|
710 |
+
*.log
|
711 |
+
local_settings.py
|
712 |
+
db.sqlite3
|
713 |
+
db.sqlite3-journal
|
714 |
+
|
715 |
+
# Flask stuff:
|
716 |
+
instance/
|
717 |
+
.webassets-cache
|
718 |
+
|
719 |
+
# Scrapy stuff:
|
720 |
+
.scrapy
|
721 |
+
|
722 |
+
# Sphinx documentation
|
723 |
+
docs/_build/
|
724 |
+
|
725 |
+
# PyBuilder
|
726 |
+
.pybuilder/
|
727 |
+
target/
|
728 |
+
|
729 |
+
# Jupyter Notebook
|
730 |
+
.ipynb_checkpoints
|
731 |
+
|
732 |
+
# IPython
|
733 |
+
profile_default/
|
734 |
+
ipython_config.py
|
735 |
+
|
736 |
+
# pyenv
|
737 |
+
# For a library or package, you might want to ignore these files since the code is
|
738 |
+
# intended to run in multiple environments; otherwise, check them in:
|
739 |
+
# .python-version
|
740 |
+
|
741 |
+
# pipenv
|
742 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
743 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
744 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
745 |
+
# install all needed dependencies.
|
746 |
+
#Pipfile.lock
|
747 |
+
|
748 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
749 |
+
__pypackages__/
|
750 |
+
|
751 |
+
# Celery stuff
|
752 |
+
celerybeat-schedule
|
753 |
+
celerybeat.pid
|
754 |
+
|
755 |
+
# SageMath parsed files
|
756 |
+
*.sage.py
|
757 |
+
|
758 |
+
# Environments
|
759 |
+
.env
|
760 |
+
.venv
|
761 |
+
env/
|
762 |
+
venv/
|
763 |
+
ENV/
|
764 |
+
env.bak/
|
765 |
+
venv.bak/
|
766 |
+
|
767 |
+
# Spyder project settings
|
768 |
+
.spyderproject
|
769 |
+
.spyproject
|
770 |
+
|
771 |
+
# Rope project settings
|
772 |
+
.ropeproject
|
773 |
+
|
774 |
+
# mkdocs documentation
|
775 |
+
/site
|
776 |
+
|
777 |
+
# mypy
|
778 |
+
.mypy_cache/
|
779 |
+
.dmypy.json
|
780 |
+
dmypy.json
|
781 |
+
|
782 |
+
# Pyre type checker
|
783 |
+
.pyre/
|
784 |
+
|
785 |
+
# pytype static type analyzer
|
786 |
+
.pytype/
|
787 |
+
|
788 |
+
# Cython debug symbols
|
789 |
+
cython_debug/
|
790 |
+
|
791 |
+
### JupyterNotebooks template
|
792 |
+
# gitignore template for Jupyter Notebooks
|
793 |
+
# website: http://jupyter.org/
|
794 |
+
|
795 |
+
.ipynb_checkpoints
|
796 |
+
*/.ipynb_checkpoints/*
|
797 |
+
|
798 |
+
# IPython
|
799 |
+
profile_default/
|
800 |
+
ipython_config.py
|
801 |
+
|
802 |
+
# Remove previous ipynb_checkpoints
|
803 |
+
# git rm -r .ipynb_checkpoints/
|
804 |
+
|
805 |
+
### VirtualEnv template
|
806 |
+
# Virtualenv
|
807 |
+
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
|
808 |
+
.Python
|
809 |
+
[Bb]in
|
810 |
+
[Ii]nclude
|
811 |
+
[Ll]ib
|
812 |
+
[Ll]ib64
|
813 |
+
[Ll]ocal
|
814 |
+
[Ss]cripts
|
815 |
+
pyvenv.cfg
|
816 |
+
.venv
|
817 |
+
pip-selfcheck.json
|
818 |
+
|
819 |
+
### Eclipse template
|
820 |
+
.metadata
|
821 |
+
bin/
|
822 |
+
tmp/
|
823 |
+
*.tmp
|
824 |
+
*.bak
|
825 |
+
*.swp
|
826 |
+
*~.nib
|
827 |
+
local.properties
|
828 |
+
.settings/
|
829 |
+
.loadpath
|
830 |
+
.recommenders
|
831 |
+
|
832 |
+
# External tool builders
|
833 |
+
.externalToolBuilders/
|
834 |
+
|
835 |
+
# Locally stored "Eclipse launch configurations"
|
836 |
+
*.launch
|
837 |
+
|
838 |
+
# PyDev specific (Python IDE for Eclipse)
|
839 |
+
*.pydevproject
|
840 |
+
|
841 |
+
# CDT-specific (C/C++ Development Tooling)
|
842 |
+
.cproject
|
843 |
+
|
844 |
+
# CDT- autotools
|
845 |
+
.autotools
|
846 |
+
|
847 |
+
# Java annotation processor (APT)
|
848 |
+
.factorypath
|
849 |
+
|
850 |
+
# PDT-specific (PHP Development Tools)
|
851 |
+
.buildpath
|
852 |
+
|
853 |
+
# sbteclipse plugin
|
854 |
+
.target
|
855 |
+
|
856 |
+
# Tern plugin
|
857 |
+
.tern-project
|
858 |
+
|
859 |
+
# TeXlipse plugin
|
860 |
+
.texlipse
|
861 |
+
|
862 |
+
# STS (Spring Tool Suite)
|
863 |
+
.springBeans
|
864 |
+
|
865 |
+
# Code Recommenders
|
866 |
+
.recommenders/
|
867 |
+
|
868 |
+
# Annotation Processing
|
869 |
+
.apt_generated/
|
870 |
+
.apt_generated_test/
|
871 |
+
|
872 |
+
# Scala IDE specific (Scala & Java development for Eclipse)
|
873 |
+
.cache-main
|
874 |
+
.scala_dependencies
|
875 |
+
.worksheet
|
876 |
+
|
877 |
+
# Uncomment this line if you wish to ignore the project description file.
|
878 |
+
# Typically, this file would be tracked if it contains build/dependency configurations:
|
879 |
+
#.project
|
880 |
+
|
881 |
+
# CUSTOM
|
882 |
+
.idea
|
883 |
+
.DS_Store
|
884 |
+
.DS_store
|
885 |
+
*.tmp.py
|
886 |
+
*.tmp.json
|
887 |
+
tmp
|
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 [email protected]:<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
ADDED
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Chef Transformer
|
3 |
+
emoji: 🍲
|
4 |
+
colorFrom: blue
|
5 |
+
colorTo: red
|
6 |
+
sdk: streamlit
|
7 |
+
sdk_version: 1.1.0
|
8 |
+
app_file: app.py
|
9 |
+
pinned: false
|
10 |
+
duplicated_from: flax-community/chef-transformer
|
11 |
+
---
|
12 |
+
|
13 |
+
# Chef Transformer (T5)
|
14 |
+
> 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.
|
15 |
+
|
16 |
+
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).
|
17 |
+
|
18 |
+
|
19 |
+
## Team Members
|
20 |
+
- Mehrdad Farahani ([m3hrdadfi](https://huggingface.co/m3hrdadfi))
|
21 |
+
- Kartik Godawat ([dk-crazydiv](https://huggingface.co/dk-crazydiv))
|
22 |
+
- Haswanth Aekula ([hassiahk](https://huggingface.co/hassiahk))
|
23 |
+
- Deepak Pandian ([rays2pix](https://huggingface.co/rays2pix))
|
24 |
+
- Nicholas Broad ([nbroad](https://huggingface.co/nbroad))
|
25 |
+
|
26 |
+
## Dataset
|
27 |
+
|
28 |
+
[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.
|
29 |
+
|
30 |
+
### Example
|
31 |
+
|
32 |
+
```json
|
33 |
+
{
|
34 |
+
"NER": [
|
35 |
+
"oyster crackers",
|
36 |
+
"salad dressing",
|
37 |
+
"lemon pepper",
|
38 |
+
"dill weed",
|
39 |
+
"garlic powder",
|
40 |
+
"salad oil"
|
41 |
+
],
|
42 |
+
"directions": [
|
43 |
+
"Combine salad dressing mix and oil.",
|
44 |
+
"Add dill weed, garlic powder and lemon pepper.",
|
45 |
+
"Pour over crackers; stir to coat.",
|
46 |
+
"Place in warm oven.",
|
47 |
+
"Use very low temperature for 15 to 20 minutes."
|
48 |
+
],
|
49 |
+
"ingredients": [
|
50 |
+
"12 to 16 oz. plain oyster crackers",
|
51 |
+
"1 pkg. Hidden Valley Ranch salad dressing mix",
|
52 |
+
"1/4 tsp. lemon pepper",
|
53 |
+
"1/2 to 1 tsp. dill weed",
|
54 |
+
"1/4 tsp. garlic powder",
|
55 |
+
"3/4 to 1 c. salad oil"
|
56 |
+
],
|
57 |
+
"link": "www.cookbooks.com/Recipe-Details.aspx?id=648947",
|
58 |
+
"source": "Gathered",
|
59 |
+
"title": "Hidden Valley Ranch Oyster Crackers"
|
60 |
+
}
|
61 |
+
```
|
62 |
+
|
63 |
+
## How To Use
|
64 |
+
|
65 |
+
```bash
|
66 |
+
# Installing requirements
|
67 |
+
pip install transformers
|
68 |
+
```
|
69 |
+
|
70 |
+
```python
|
71 |
+
from transformers import FlaxAutoModelForSeq2SeqLM
|
72 |
+
from transformers import AutoTokenizer
|
73 |
+
|
74 |
+
MODEL_NAME_OR_PATH = "flax-community/t5-recipe-generation"
|
75 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME_OR_PATH, use_fast=True)
|
76 |
+
model = FlaxAutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME_OR_PATH)
|
77 |
+
|
78 |
+
prefix = "items: "
|
79 |
+
# generation_kwargs = {
|
80 |
+
# "max_length": 512,
|
81 |
+
# "min_length": 64,
|
82 |
+
# "no_repeat_ngram_size": 3,
|
83 |
+
# "early_stopping": True,
|
84 |
+
# "num_beams": 5,
|
85 |
+
# "length_penalty": 1.5,
|
86 |
+
# }
|
87 |
+
generation_kwargs = {
|
88 |
+
"max_length": 512,
|
89 |
+
"min_length": 64,
|
90 |
+
"no_repeat_ngram_size": 3,
|
91 |
+
"do_sample": True,
|
92 |
+
"top_k": 60,
|
93 |
+
"top_p": 0.95
|
94 |
+
}
|
95 |
+
|
96 |
+
|
97 |
+
special_tokens = tokenizer.all_special_tokens
|
98 |
+
tokens_map = {
|
99 |
+
"<sep>": "--",
|
100 |
+
"<section>": "\n"
|
101 |
+
}
|
102 |
+
def skip_special_tokens(text, special_tokens):
|
103 |
+
for token in special_tokens:
|
104 |
+
text = text.replace(token, "")
|
105 |
+
|
106 |
+
return text
|
107 |
+
|
108 |
+
def target_postprocessing(texts, special_tokens):
|
109 |
+
if not isinstance(texts, list):
|
110 |
+
texts = [texts]
|
111 |
+
|
112 |
+
new_texts = []
|
113 |
+
for text in texts:
|
114 |
+
text = skip_special_tokens(text, special_tokens)
|
115 |
+
|
116 |
+
for k, v in tokens_map.items():
|
117 |
+
text = text.replace(k, v)
|
118 |
+
|
119 |
+
new_texts.append(text)
|
120 |
+
|
121 |
+
return new_texts
|
122 |
+
|
123 |
+
def generation_function(texts):
|
124 |
+
_inputs = texts if isinstance(texts, list) else [texts]
|
125 |
+
inputs = [prefix + inp for inp in _inputs]
|
126 |
+
inputs = tokenizer(
|
127 |
+
inputs,
|
128 |
+
max_length=256,
|
129 |
+
padding="max_length",
|
130 |
+
truncation=True,
|
131 |
+
return_tensors="jax"
|
132 |
+
)
|
133 |
+
|
134 |
+
input_ids = inputs.input_ids
|
135 |
+
attention_mask = inputs.attention_mask
|
136 |
+
|
137 |
+
output_ids = model.generate(
|
138 |
+
input_ids=input_ids,
|
139 |
+
attention_mask=attention_mask,
|
140 |
+
**generation_kwargs
|
141 |
+
)
|
142 |
+
generated = output_ids.sequences
|
143 |
+
generated_recipe = target_postprocessing(
|
144 |
+
tokenizer.batch_decode(generated, skip_special_tokens=False),
|
145 |
+
special_tokens
|
146 |
+
)
|
147 |
+
return generated_recipe
|
148 |
+
```
|
149 |
+
|
150 |
+
```python
|
151 |
+
items = [
|
152 |
+
"macaroni, butter, salt, bacon, milk, flour, pepper, cream corn",
|
153 |
+
"provolone cheese, bacon, bread, ginger"
|
154 |
+
]
|
155 |
+
generated = generation_function(items)
|
156 |
+
for text in generated:
|
157 |
+
sections = text.split("\n")
|
158 |
+
for section in sections:
|
159 |
+
section = section.strip()
|
160 |
+
if section.startswith("title:"):
|
161 |
+
section = section.replace("title:", "")
|
162 |
+
headline = "TITLE"
|
163 |
+
elif section.startswith("ingredients:"):
|
164 |
+
section = section.replace("ingredients:", "")
|
165 |
+
headline = "INGREDIENTS"
|
166 |
+
elif section.startswith("directions:"):
|
167 |
+
section = section.replace("directions:", "")
|
168 |
+
headline = "DIRECTIONS"
|
169 |
+
|
170 |
+
if headline == "TITLE":
|
171 |
+
print(f"[{headline}]: {section.strip().capitalize()}")
|
172 |
+
else:
|
173 |
+
section_info = [f" - {i+1}: {info.strip().capitalize()}" for i, info in enumerate(section.split("--"))]
|
174 |
+
print(f"[{headline}]:")
|
175 |
+
print("\n".join(section_info))
|
176 |
+
|
177 |
+
print("-" * 130)
|
178 |
+
```
|
179 |
+
|
180 |
+
Output:
|
181 |
+
```text
|
182 |
+
[TITLE]: Macaroni and corn
|
183 |
+
[INGREDIENTS]:
|
184 |
+
- 1: 2 c. macaroni
|
185 |
+
- 2: 2 tbsp. butter
|
186 |
+
- 3: 1 tsp. salt
|
187 |
+
- 4: 4 slices bacon
|
188 |
+
- 5: 2 c. milk
|
189 |
+
- 6: 2 tbsp. flour
|
190 |
+
- 7: 1/4 tsp. pepper
|
191 |
+
- 8: 1 can cream corn
|
192 |
+
[DIRECTIONS]:
|
193 |
+
- 1: Cook macaroni in boiling salted water until tender.
|
194 |
+
- 2: Drain.
|
195 |
+
- 3: Melt butter in saucepan.
|
196 |
+
- 4: Blend in flour, salt and pepper.
|
197 |
+
- 5: Add milk all at once.
|
198 |
+
- 6: Cook and stir until thickened and bubbly.
|
199 |
+
- 7: Stir in corn and bacon.
|
200 |
+
- 8: Pour over macaroni and mix well.
|
201 |
+
--------------------------------------------------------------------------------------------------------------------------------
|
202 |
+
[TITLE]: Grilled provolone and bacon sandwich
|
203 |
+
[INGREDIENTS]:
|
204 |
+
- 1: 2 slices provolone cheese
|
205 |
+
- 2: 2 slices bacon
|
206 |
+
- 3: 2 slices sourdough bread
|
207 |
+
- 4: 2 slices pickled ginger
|
208 |
+
[DIRECTIONS]:
|
209 |
+
- 1: Place a slice of provolone cheese on one slice of bread.
|
210 |
+
- 2: Top with a slice of bacon.
|
211 |
+
- 3: Top with a slice of pickled ginger.
|
212 |
+
- 4: Top with the other slice of bread.
|
213 |
+
- 5: Heat a skillet over medium heat.
|
214 |
+
- 6: Place the sandwich in the skillet and cook until the cheese is melted and the bread is golden brown.
|
215 |
+
--------------------------------------------------------------------------------------------------------------------------------
|
216 |
+
```
|
217 |
+
|
218 |
+
## Evaluation
|
219 |
+
...
|
220 |
+
|
221 |
+
### Result
|
222 |
+
Since the test set is not available, we will evaluate the model based on a shared test set. This test set consists of 5% of the whole test (*= 5,000 records*),
|
223 |
+
and we will generate five recipes for each input(*= 25,000 records*).
|
224 |
+
The following table summarizes the scores obtained by the **Chef Transformer** and **RecipeNLG** as our baseline.
|
225 |
+
|
226 |
+
| Model | COSIM | WER | ROUGE-2 | BLEU | GLEU | METEOR |
|
227 |
+
|:--------------------------------------------------------------------------------:|:----------:|:----------:|:----------:|:----------:|:----------:|:----------:|
|
228 |
+
| [RecipeNLG](https://huggingface.co/mbien/recipenlg) | 0.5723 | 1.2125 | 0.1354 | 0.1164 | 0.1503 | 0.2309 |
|
229 |
+
| [Chef Transformer](https://huggingface.co/flax-community/t5-recipe-generation) * | **0.7282** | **0.7613** | **0.2470** | **0.3245** | **0.2624** | **0.4150** |
|
230 |
+
|
231 |
+
*From the 5 generated recipes corresponding to each NER (food items), only the highest score was taken into account in the WER, COSIM, and ROUGE metrics. At the same time, BLEU, GLEU, Meteor were designed to have many possible references.*
|
232 |
+
|
233 |
+
## Streamlit demo
|
234 |
+
|
235 |
+
```bash
|
236 |
+
streamlit run app.py
|
237 |
+
```
|
238 |
+
|
239 |
+
## Looking to contribute?
|
240 |
+
Then follow the steps mentioned in this [contributing guide](CONTRIBUTING.md) and you are good to go.
|
241 |
+
|
242 |
+
## Copyright
|
243 |
+
|
244 |
+
Special thanks to those who provided these fantastic materials.
|
245 |
+
- [Anatomy](https://www.flaticon.com/free-icon)
|
246 |
+
- [Chef Hat](https://www.vecteezy.com/members/jellyfishwater)
|
247 |
+
- [Moira Nazzari](https://pixabay.com/photos/food-dessert-cake-eggs-butter-3048440/)
|
248 |
+
- [Instagram Post](https://www.freepik.com/free-psd/recipes-ad-social-media-post-template_11520617.htm)
|
app.py
ADDED
@@ -0,0 +1,320 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
import torch
|
4 |
+
from transformers import pipeline, set_seed
|
5 |
+
from transformers import AutoTokenizer
|
6 |
+
|
7 |
+
from PIL import (
|
8 |
+
ImageFont,
|
9 |
+
)
|
10 |
+
|
11 |
+
import os
|
12 |
+
import re
|
13 |
+
import random
|
14 |
+
import textwrap
|
15 |
+
from examples import EXAMPLES
|
16 |
+
import dummy
|
17 |
+
import meta
|
18 |
+
from utils import ext
|
19 |
+
from utils.api import generate_cook_image
|
20 |
+
from utils.draw import generate_food_with_logo_image, generate_recipe_image
|
21 |
+
from utils.st import (
|
22 |
+
remote_css,
|
23 |
+
local_css,
|
24 |
+
|
25 |
+
)
|
26 |
+
from utils.utils import (
|
27 |
+
load_image_from_url,
|
28 |
+
load_image_from_local,
|
29 |
+
image_to_base64,
|
30 |
+
pure_comma_separation
|
31 |
+
)
|
32 |
+
|
33 |
+
|
34 |
+
class TextGeneration:
|
35 |
+
def __init__(self):
|
36 |
+
self.debug = False
|
37 |
+
self.dummy_outputs = dummy.recipes
|
38 |
+
self.tokenizer = None
|
39 |
+
self.generator = None
|
40 |
+
self.api_ids = []
|
41 |
+
self.api_keys = []
|
42 |
+
self.api_test = 2
|
43 |
+
self.task = "text2text-generation"
|
44 |
+
self.model_name_or_path = "flax-community/t5-recipe-generation"
|
45 |
+
self.color_frame = "#ffffff"
|
46 |
+
self.main_frame = "asset/frame/recipe-bg.png"
|
47 |
+
self.no_food = "asset/frame/no_food.png"
|
48 |
+
self.logo_frame = "asset/frame/logo.png"
|
49 |
+
self.chef_frames = {
|
50 |
+
"scheherazade": "asset/frame/food-image-logo-bg-s.png",
|
51 |
+
"giovanni": "asset/frame/food-image-logo-bg-g.png",
|
52 |
+
}
|
53 |
+
self.fonts = {
|
54 |
+
"title": ImageFont.truetype("asset/fonts/Poppins-Bold.ttf", 70),
|
55 |
+
"sub_title": ImageFont.truetype("asset/fonts/Poppins-Medium.ttf", 30),
|
56 |
+
"body_bold": ImageFont.truetype("asset/fonts/Montserrat-Bold.ttf", 22),
|
57 |
+
"body": ImageFont.truetype("asset/fonts/Montserrat-Regular.ttf", 18),
|
58 |
+
|
59 |
+
}
|
60 |
+
set_seed(42)
|
61 |
+
|
62 |
+
def _skip_special_tokens_and_prettify(self, text):
|
63 |
+
recipe_maps = {"<sep>": "--", "<section>": "\n"}
|
64 |
+
recipe_map_pattern = "|".join(map(re.escape, recipe_maps.keys()))
|
65 |
+
|
66 |
+
text = re.sub(
|
67 |
+
recipe_map_pattern,
|
68 |
+
lambda m: recipe_maps[m.group()],
|
69 |
+
re.sub("|".join(self.tokenizer.all_special_tokens), "", text)
|
70 |
+
)
|
71 |
+
|
72 |
+
data = {"title": "", "ingredients": [], "directions": []}
|
73 |
+
for section in text.split("\n"):
|
74 |
+
section = section.strip()
|
75 |
+
if section.startswith("title:"):
|
76 |
+
data["title"] = " ".join(
|
77 |
+
[w.strip().capitalize() for w in section.replace("title:", "").strip().split() if w.strip()]
|
78 |
+
)
|
79 |
+
elif section.startswith("ingredients:"):
|
80 |
+
data["ingredients"] = [s.strip() for s in section.replace("ingredients:", "").split('--')]
|
81 |
+
elif section.startswith("directions:"):
|
82 |
+
data["directions"] = [s.strip() for s in section.replace("directions:", "").split('--')]
|
83 |
+
else:
|
84 |
+
pass
|
85 |
+
|
86 |
+
return data
|
87 |
+
|
88 |
+
def load_pipeline(self):
|
89 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name_or_path)
|
90 |
+
self.generator = pipeline(self.task, model=self.model_name_or_path, tokenizer=self.model_name_or_path)
|
91 |
+
|
92 |
+
def load_api(self):
|
93 |
+
app_ids = os.getenv("EDAMAM_APP_ID")
|
94 |
+
app_ids = app_ids.split(",") if app_ids else []
|
95 |
+
app_keys = os.getenv("EDAMAM_APP_KEY")
|
96 |
+
app_keys = app_keys.split(",") if app_keys else []
|
97 |
+
|
98 |
+
if len(app_ids) != len(app_keys):
|
99 |
+
self.api_ids = []
|
100 |
+
self.api_keys = []
|
101 |
+
|
102 |
+
self.api_ids = app_ids
|
103 |
+
self.api_keys = app_keys
|
104 |
+
|
105 |
+
def load(self):
|
106 |
+
self.load_api()
|
107 |
+
if not self.debug:
|
108 |
+
self.load_pipeline()
|
109 |
+
|
110 |
+
def prepare_frame(self, recipe, chef_name):
|
111 |
+
frame_path = self.chef_frames[chef_name.lower()]
|
112 |
+
food_logo = generate_food_with_logo_image(frame_path, self.logo_frame, recipe["image"])
|
113 |
+
frame = generate_recipe_image(
|
114 |
+
recipe,
|
115 |
+
self.main_frame,
|
116 |
+
food_logo,
|
117 |
+
self.fonts,
|
118 |
+
bg_color="#ffffff"
|
119 |
+
)
|
120 |
+
return frame
|
121 |
+
|
122 |
+
def generate(self, items, generation_kwargs):
|
123 |
+
recipe = self.dummy_outputs[0]
|
124 |
+
# recipe = self.dummy_outputs[random.randint(0, len(self.dummy_outputs) - 1)]
|
125 |
+
|
126 |
+
if not self.debug:
|
127 |
+
generation_kwargs["num_return_sequences"] = 1
|
128 |
+
# generation_kwargs["return_full_text"] = False
|
129 |
+
generation_kwargs["return_tensors"] = True
|
130 |
+
generation_kwargs["return_text"] = False
|
131 |
+
|
132 |
+
generated_ids = self.generator(
|
133 |
+
items,
|
134 |
+
**generation_kwargs,
|
135 |
+
)[0]["generated_token_ids"]
|
136 |
+
recipe = self.tokenizer.decode(generated_ids, skip_special_tokens=False)
|
137 |
+
recipe = self._skip_special_tokens_and_prettify(recipe)
|
138 |
+
|
139 |
+
if self.api_ids and self.api_keys and len(self.api_ids) == len(self.api_keys):
|
140 |
+
test = 0
|
141 |
+
for i in range(len(self.api_keys)):
|
142 |
+
if test > self.api_test:
|
143 |
+
recipe["image"] = None
|
144 |
+
break
|
145 |
+
image = generate_cook_image(recipe["title"].lower(), self.api_ids[i], self.api_keys[i])
|
146 |
+
test += 1
|
147 |
+
if image:
|
148 |
+
recipe["image"] = image
|
149 |
+
break
|
150 |
+
else:
|
151 |
+
recipe["image"] = None
|
152 |
+
|
153 |
+
return recipe
|
154 |
+
|
155 |
+
def generate_frame(self, recipe, chef_name):
|
156 |
+
return self.prepare_frame(recipe, chef_name)
|
157 |
+
|
158 |
+
|
159 |
+
@st.cache(allow_output_mutation=True)
|
160 |
+
def load_text_generator():
|
161 |
+
generator = TextGeneration()
|
162 |
+
generator.load()
|
163 |
+
return generator
|
164 |
+
|
165 |
+
|
166 |
+
chef_top = {
|
167 |
+
"max_length": 512,
|
168 |
+
"min_length": 64,
|
169 |
+
"no_repeat_ngram_size": 3,
|
170 |
+
"do_sample": True,
|
171 |
+
"top_k": 60,
|
172 |
+
"top_p": 0.95,
|
173 |
+
"num_return_sequences": 1
|
174 |
+
}
|
175 |
+
chef_beam = {
|
176 |
+
"max_length": 512,
|
177 |
+
"min_length": 64,
|
178 |
+
"no_repeat_ngram_size": 3,
|
179 |
+
"early_stopping": True,
|
180 |
+
"num_beams": 5,
|
181 |
+
"length_penalty": 1.5,
|
182 |
+
"num_return_sequences": 1
|
183 |
+
}
|
184 |
+
|
185 |
+
|
186 |
+
def main():
|
187 |
+
st.set_page_config(
|
188 |
+
page_title="Chef Transformer",
|
189 |
+
page_icon="🍲",
|
190 |
+
layout="wide",
|
191 |
+
initial_sidebar_state="expanded"
|
192 |
+
)
|
193 |
+
generator = load_text_generator()
|
194 |
+
# if hasattr(st, "session_state"):
|
195 |
+
# if 'get_random_frame' not in st.session_state:
|
196 |
+
# st.session_state.get_random_frame = generator.frames[0]
|
197 |
+
# else:
|
198 |
+
# get_random_frame = generator.frames[0]
|
199 |
+
|
200 |
+
remote_css("https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600&family=Poppins:wght@600&display=swap")
|
201 |
+
local_css("asset/css/style.css")
|
202 |
+
|
203 |
+
col1, col2 = st.columns([6, 4])
|
204 |
+
with col2:
|
205 |
+
st.image(load_image_from_local("asset/images/chef-transformer-transparent.png"), width=300)
|
206 |
+
st.markdown(meta.SIDEBAR_INFO, unsafe_allow_html=True)
|
207 |
+
|
208 |
+
with st.expander("Where did this story start?", expanded=True):
|
209 |
+
st.markdown(meta.STORY, unsafe_allow_html=True)
|
210 |
+
|
211 |
+
with col1:
|
212 |
+
st.markdown(meta.HEADER_INFO, unsafe_allow_html=True)
|
213 |
+
|
214 |
+
st.markdown(meta.CHEF_INFO, unsafe_allow_html=True)
|
215 |
+
chef = st.selectbox("Choose your chef", index=0, options=["Chef Scheherazade", "Chef Giovanni"])
|
216 |
+
|
217 |
+
prompts = list(EXAMPLES.keys()) + ["Custom"]
|
218 |
+
prompt = st.selectbox(
|
219 |
+
'Examples (select from this list)',
|
220 |
+
prompts,
|
221 |
+
# index=len(prompts) - 1,
|
222 |
+
index=0
|
223 |
+
)
|
224 |
+
|
225 |
+
if prompt == "Custom":
|
226 |
+
prompt_box = ""
|
227 |
+
else:
|
228 |
+
prompt_box = EXAMPLES[prompt]
|
229 |
+
|
230 |
+
items = st.text_area(
|
231 |
+
'Insert your food items here (separated by `,`): ',
|
232 |
+
pure_comma_separation(prompt_box, return_list=False),
|
233 |
+
)
|
234 |
+
items = pure_comma_separation(items, return_list=False)
|
235 |
+
entered_items = st.empty()
|
236 |
+
|
237 |
+
recipe_button = st.button('Get Recipe!')
|
238 |
+
|
239 |
+
st.markdown(
|
240 |
+
"<hr />",
|
241 |
+
unsafe_allow_html=True
|
242 |
+
)
|
243 |
+
if recipe_button:
|
244 |
+
# if hasattr(st, "session_state"):
|
245 |
+
# st.session_state.get_random_frame = generator.frames[random.randint(0, len(generator.frames)) - 1]
|
246 |
+
# else:
|
247 |
+
# get_random_frame = generator.frames[random.randint(0, len(generator.frames)) - 1]
|
248 |
+
|
249 |
+
entered_items.markdown("**Generate recipe for:** " + items)
|
250 |
+
with st.spinner("Generating recipe..."):
|
251 |
+
|
252 |
+
if not isinstance(items, str) or not len(items) > 1:
|
253 |
+
entered_items.markdown(
|
254 |
+
f"**{chef}** would like to know what ingredients do you like to use in "
|
255 |
+
f"your food? "
|
256 |
+
)
|
257 |
+
else:
|
258 |
+
gen_kw = chef_top if chef == "Chef Scheherazade" else chef_beam
|
259 |
+
generated_recipe = generator.generate(items, gen_kw)
|
260 |
+
|
261 |
+
title = generated_recipe["title"]
|
262 |
+
food_image = generated_recipe["image"]
|
263 |
+
food_image = load_image_from_url(food_image, rgba_mode=True, default_image=generator.no_food)
|
264 |
+
food_image = image_to_base64(food_image)
|
265 |
+
|
266 |
+
ingredients = ext.ingredients(
|
267 |
+
generated_recipe["ingredients"],
|
268 |
+
pure_comma_separation(items, return_list=True)
|
269 |
+
)
|
270 |
+
# ingredients = [textwrap.fill(item, 10).replace("\n", "<br /> ") for item in ingredients]
|
271 |
+
|
272 |
+
directions = ext.directions(generated_recipe["directions"])
|
273 |
+
# directions = [textwrap.fill(item, 70).replace("\n", "<br /> ") for item in directions]
|
274 |
+
|
275 |
+
generated_recipe["by"] = chef
|
276 |
+
|
277 |
+
r1, r2 = st.columns([6, 2])
|
278 |
+
|
279 |
+
with r2:
|
280 |
+
# st.write(st.session_state.get_random_frame)
|
281 |
+
# if hasattr(st, "session_state"):
|
282 |
+
# recipe_post = generator.generate_frame(generated_recipe, st.session_state.get_random_frame)
|
283 |
+
# else:
|
284 |
+
# recipe_post = generator.generate_frame(generated_recipe, get_random_frame)
|
285 |
+
|
286 |
+
recipe_post = generator.generate_frame(generated_recipe, chef.split()[-1])
|
287 |
+
|
288 |
+
st.image(
|
289 |
+
recipe_post,
|
290 |
+
# width=500,
|
291 |
+
caption="Save image and share on your social media",
|
292 |
+
use_column_width="auto",
|
293 |
+
output_format="PNG"
|
294 |
+
)
|
295 |
+
|
296 |
+
with r1:
|
297 |
+
st.markdown(
|
298 |
+
" ".join([
|
299 |
+
"<div class='r-text-recipe'>",
|
300 |
+
"<div class='food-title'>",
|
301 |
+
f"<img src='{food_image}' />",
|
302 |
+
f"<h2 class='font-title text-bold'>{title}</h2>",
|
303 |
+
"</div>",
|
304 |
+
'<div class="divider"><div class="divider-mask"></div></div>',
|
305 |
+
"<h3 class='ingredients font-body text-bold'>Ingredients</h3>",
|
306 |
+
"<ul class='ingredients-list font-body'>",
|
307 |
+
" ".join([f'<li>{item}</li>' for item in ingredients]),
|
308 |
+
"</ul>",
|
309 |
+
"<h3 class='directions font-body text-bold'>Directions</h3>",
|
310 |
+
"<ol class='ingredients-list font-body'>",
|
311 |
+
" ".join([f'<li>{item}</li>' for item in directions]),
|
312 |
+
"</ol>",
|
313 |
+
"</div>"
|
314 |
+
]),
|
315 |
+
unsafe_allow_html=True
|
316 |
+
)
|
317 |
+
|
318 |
+
|
319 |
+
if __name__ == '__main__':
|
320 |
+
main()
|
asset/css/style.css
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
body {
|
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;
|
20 |
+
justify-content: center;
|
21 |
+
}
|
22 |
+
.comma:not(:empty) ~ .comma:not(:empty):before {
|
23 |
+
content: ", ";
|
24 |
+
}
|
25 |
+
.strong {
|
26 |
+
font-weight: bold;
|
27 |
+
}
|
28 |
+
.d-block {
|
29 |
+
display: block;
|
30 |
+
}
|
31 |
+
.extra-info {
|
32 |
+
font-weight: normal;
|
33 |
+
font-style: italic;
|
34 |
+
font-size: small;
|
35 |
+
}
|
36 |
+
|
37 |
+
.contributors {
|
38 |
+
margin-bottom: 10px;
|
39 |
+
border-bottom: 1px solid #f3f3f3;
|
40 |
+
padding-bottom: 10px;
|
41 |
+
}
|
42 |
+
.contributors a.contributor {
|
43 |
+
text-decoration: none;
|
44 |
+
color: #585858;
|
45 |
+
}
|
46 |
+
.contributors a.contributor:hover {
|
47 |
+
text-decoration: underline;
|
48 |
+
}
|
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;
|
73 |
+
}
|
74 |
+
|
75 |
+
.divider-mask {
|
76 |
+
overflow: hidden;
|
77 |
+
height: 20px;
|
78 |
+
}
|
79 |
+
|
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 |
+
}
|
asset/fonts/Montserrat-Bold.ttf
ADDED
Binary file (244 kB). View file
|
|
asset/fonts/Montserrat-Regular.ttf
ADDED
Binary file (246 kB). View file
|
|
asset/fonts/Poppins-Bold.ttf
ADDED
Binary file (154 kB). View file
|
|
asset/fonts/Poppins-Medium.ttf
ADDED
Binary file (156 kB). View file
|
|
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
![]() |
asset/images/chef-transformer-transparent.png
ADDED
![]() |
asset/images/chef-transformer.png
ADDED
![]() |
asset/images/frames/1.jpg
ADDED
![]() |
asset/images/frames/10.jpg
ADDED
![]() |
asset/images/frames/2.jpg
ADDED
![]() |
asset/images/frames/3.jpg
ADDED
![]() |
asset/images/frames/4.jpg
ADDED
![]() |
asset/images/frames/5.jpg
ADDED
![]() |
asset/images/frames/6.jpg
ADDED
![]() |
asset/images/frames/7.jpg
ADDED
![]() |
asset/images/frames/8.jpg
ADDED
![]() |
asset/images/frames/9.jpg
ADDED
![]() |
asset/images/logo.png
ADDED
![]() |
asset/images/recipe-frame.png
ADDED
![]() |
asset/images/recipe-post.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 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 |
+
]
|
examples.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
EXAMPLES = {
|
2 |
+
"Turkish Food 1": "phyllo dough, unsalted butter, walnuts, cinnamon, water, honey, melted chocolate",
|
3 |
+
"Persian Food 1": "beef, oil, onion, tomatoes, turmeric powder, limes, water, salt, pepper, red beans, herb",
|
4 |
+
"Persian Food 2": "beef, potatoes, eggs, onion, flour, turmeric powder, oil, salt, pepper",
|
5 |
+
"Persian Food 3": "walnut pieces, onion, boneless skinless chicken thighs, pomegranate molasses, orange juice, chicken stock, cinnamon, salt, pepper, oil",
|
6 |
+
"Turkish Food 2": "water, dry active yeast, flour, salt, olive oil, ground beef, onion, pepper, salt, feta cheese, parsley, lemon",
|
7 |
+
"Korean Food 1": "pork chops, soy sauce, honey, garlic, sesame oil, fresh ginger root, sweet chili sauce, olive oil",
|
8 |
+
"Italian Food 1": "rotini pasta, broccoli florets, parmesan cheese, red pepper, onions, black olives",
|
9 |
+
"German Food 1": "oil, boneless pork chops, salt, pepper, flour, eggs, breadcrumbs, gravy, parsley"
|
10 |
+
}
|
meta.py
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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>
|
7 |
+
<a class="contributor comma" href="https://huggingface.co/rays2pix">Deepak Pandian</a>
|
8 |
+
<a class="contributor comma" href="https://huggingface.co/nbroad">Nicholas Broad</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>.
|
24 |
+
Both of my students participated in my rigorous culinary program, <a href="https://huggingface.co/flax-community/t5-recipe-generation">T5 fine-tuning</a>,
|
25 |
+
to learn how to prepare exquisite cuisines from a wide variety of ingredients.
|
26 |
+
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>.
|
27 |
+
If you give each of them the same ingredients, they'll usually come up with something different. <br /><br />
|
28 |
+
At the start of the program the chefs read cookbooks containing thousands of recipes of varying difficulties and from cultures all over the world.
|
29 |
+
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.
|
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]
|
37 |
+
title: {TITLE} <section>
|
38 |
+
ingredients: {INGREDIENTS: separated by <sep>} <section>
|
39 |
+
directions: {DIRECTIONS: separated by <sep>}.
|
40 |
+
</pre>
|
41 |
+
|
42 |
+
<p>
|
43 |
+
<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>
|
44 |
+
</p>
|
45 |
+
<p>
|
46 |
+
In the span of a week, my chefs went from spitting out nonsense to creating masterpieces.
|
47 |
+
Their learning rate was exceptionally high and each batch of recipes was better than the last. <br />
|
48 |
+
In their final exam, they achieved <a href="https://huggingface.co/flax-community/t5-recipe-generation#evaluation">high scores</a> 💯 in a
|
49 |
+
standardized industry test and established this restaurant 🍲. Please tell your friends and family about us!
|
50 |
+
We create each recipe with a smile on our faces 🤗 Everyone at the restaurant is grateful for the generous support of
|
51 |
+
HuggingFace and Google for hosting Flax Community week.
|
52 |
+
</p>
|
53 |
+
|
54 |
+
</div>
|
55 |
+
""".strip()
|
notes/Build Ingredients Vocab.ipynb
ADDED
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 3,
|
6 |
+
"metadata": {
|
7 |
+
"ExecuteTime": {
|
8 |
+
"end_time": "2021-07-14T12:54:01.369853Z",
|
9 |
+
"start_time": "2021-07-14T12:49:27.961404Z"
|
10 |
+
}
|
11 |
+
},
|
12 |
+
"outputs": [
|
13 |
+
{
|
14 |
+
"name": "stderr",
|
15 |
+
"output_type": "stream",
|
16 |
+
"text": [
|
17 |
+
"Using custom data configuration default-fdc6acb780b42528\n"
|
18 |
+
]
|
19 |
+
},
|
20 |
+
{
|
21 |
+
"name": "stdout",
|
22 |
+
"output_type": "stream",
|
23 |
+
"text": [
|
24 |
+
"Downloading and preparing dataset recipe_nlg/default (download: Unknown size, generated: 2.04 GiB, post-processed: Unknown size, total: 2.04 GiB) to /home/rtx/.cache/huggingface/datasets/recipe_nlg/default-fdc6acb780b42528/1.0.0/20c969e1192265af03a7186457bdb4a9109d5d68b92cad04c3ec894d6e5aee61...\n"
|
25 |
+
]
|
26 |
+
},
|
27 |
+
{
|
28 |
+
"data": {
|
29 |
+
"application/vnd.jupyter.widget-view+json": {
|
30 |
+
"model_id": "",
|
31 |
+
"version_major": 2,
|
32 |
+
"version_minor": 0
|
33 |
+
},
|
34 |
+
"text/plain": [
|
35 |
+
"HBox(children=(IntProgress(value=1, bar_style='info', max=1), HTML(value='')))"
|
36 |
+
]
|
37 |
+
},
|
38 |
+
"metadata": {},
|
39 |
+
"output_type": "display_data"
|
40 |
+
},
|
41 |
+
{
|
42 |
+
"name": "stdout",
|
43 |
+
"output_type": "stream",
|
44 |
+
"text": [
|
45 |
+
"\r",
|
46 |
+
"Dataset recipe_nlg downloaded and prepared to /home/rtx/.cache/huggingface/datasets/recipe_nlg/default-fdc6acb780b42528/1.0.0/20c969e1192265af03a7186457bdb4a9109d5d68b92cad04c3ec894d6e5aee61. Subsequent calls will reuse this data.\n"
|
47 |
+
]
|
48 |
+
}
|
49 |
+
],
|
50 |
+
"source": [
|
51 |
+
"from datasets import load_dataset\n",
|
52 |
+
"DATA_DIR = \"~/Downloads/dataset/\"\n",
|
53 |
+
"dataset = load_dataset(\"recipe_nlg\", data_dir=DATA_DIR)"
|
54 |
+
]
|
55 |
+
},
|
56 |
+
{
|
57 |
+
"cell_type": "code",
|
58 |
+
"execution_count": 10,
|
59 |
+
"metadata": {
|
60 |
+
"ExecuteTime": {
|
61 |
+
"end_time": "2021-07-14T12:58:25.150105Z",
|
62 |
+
"start_time": "2021-07-14T12:55:27.486385Z"
|
63 |
+
}
|
64 |
+
},
|
65 |
+
"outputs": [
|
66 |
+
{
|
67 |
+
"name": "stderr",
|
68 |
+
"output_type": "stream",
|
69 |
+
"text": [
|
70 |
+
"100%|██████████| 2231142/2231142 [02:57<00:00, 12558.59it/s]\n"
|
71 |
+
]
|
72 |
+
}
|
73 |
+
],
|
74 |
+
"source": [
|
75 |
+
"from collections import Counter\n",
|
76 |
+
"from tqdm import tqdm\n",
|
77 |
+
"ctr = Counter()\n",
|
78 |
+
"\n",
|
79 |
+
"for row in tqdm(dataset[\"train\"]):\n",
|
80 |
+
" for item in row[\"ner\"]:\n",
|
81 |
+
" ctr[item] += 1"
|
82 |
+
]
|
83 |
+
},
|
84 |
+
{
|
85 |
+
"cell_type": "code",
|
86 |
+
"execution_count": 23,
|
87 |
+
"metadata": {
|
88 |
+
"ExecuteTime": {
|
89 |
+
"end_time": "2021-07-14T13:02:09.315817Z",
|
90 |
+
"start_time": "2021-07-14T13:02:09.259046Z"
|
91 |
+
}
|
92 |
+
},
|
93 |
+
"outputs": [],
|
94 |
+
"source": [
|
95 |
+
"first_500 = list(set([x[0].lower() for x in ctr.most_common()[0:500]]))"
|
96 |
+
]
|
97 |
+
},
|
98 |
+
{
|
99 |
+
"cell_type": "code",
|
100 |
+
"execution_count": 25,
|
101 |
+
"metadata": {
|
102 |
+
"ExecuteTime": {
|
103 |
+
"end_time": "2021-07-14T13:02:28.864546Z",
|
104 |
+
"start_time": "2021-07-14T13:02:28.856279Z"
|
105 |
+
}
|
106 |
+
},
|
107 |
+
"outputs": [
|
108 |
+
{
|
109 |
+
"data": {
|
110 |
+
"text/plain": [
|
111 |
+
"443"
|
112 |
+
]
|
113 |
+
},
|
114 |
+
"execution_count": 25,
|
115 |
+
"metadata": {},
|
116 |
+
"output_type": "execute_result"
|
117 |
+
}
|
118 |
+
],
|
119 |
+
"source": [
|
120 |
+
"len(first_500)"
|
121 |
+
]
|
122 |
+
},
|
123 |
+
{
|
124 |
+
"cell_type": "code",
|
125 |
+
"execution_count": 26,
|
126 |
+
"metadata": {
|
127 |
+
"ExecuteTime": {
|
128 |
+
"end_time": "2021-07-14T13:02:53.656711Z",
|
129 |
+
"start_time": "2021-07-14T13:02:53.653868Z"
|
130 |
+
}
|
131 |
+
},
|
132 |
+
"outputs": [],
|
133 |
+
"source": [
|
134 |
+
"first_100 = sorted(first_500[:100])\n",
|
135 |
+
"next_100 = sorted(first_500[100:200])"
|
136 |
+
]
|
137 |
+
},
|
138 |
+
{
|
139 |
+
"cell_type": "code",
|
140 |
+
"execution_count": 29,
|
141 |
+
"metadata": {
|
142 |
+
"ExecuteTime": {
|
143 |
+
"end_time": "2021-07-14T13:03:35.640538Z",
|
144 |
+
"start_time": "2021-07-14T13:03:35.634368Z"
|
145 |
+
}
|
146 |
+
},
|
147 |
+
"outputs": [],
|
148 |
+
"source": [
|
149 |
+
"d = {\n",
|
150 |
+
" \"first_100\": first_100,\n",
|
151 |
+
" \"next_100\": next_100\n",
|
152 |
+
"}"
|
153 |
+
]
|
154 |
+
},
|
155 |
+
{
|
156 |
+
"cell_type": "code",
|
157 |
+
"execution_count": 31,
|
158 |
+
"metadata": {
|
159 |
+
"ExecuteTime": {
|
160 |
+
"end_time": "2021-07-14T13:03:52.682190Z",
|
161 |
+
"start_time": "2021-07-14T13:03:52.679624Z"
|
162 |
+
}
|
163 |
+
},
|
164 |
+
"outputs": [],
|
165 |
+
"source": [
|
166 |
+
"import json\n",
|
167 |
+
"with open(\"config.json\", \"w\") as f:\n",
|
168 |
+
" f.write(json.dumps(d))"
|
169 |
+
]
|
170 |
+
},
|
171 |
+
{
|
172 |
+
"cell_type": "code",
|
173 |
+
"execution_count": null,
|
174 |
+
"metadata": {},
|
175 |
+
"outputs": [],
|
176 |
+
"source": []
|
177 |
+
}
|
178 |
+
],
|
179 |
+
"metadata": {
|
180 |
+
"kernelspec": {
|
181 |
+
"display_name": "Python 3",
|
182 |
+
"language": "python",
|
183 |
+
"name": "python3"
|
184 |
+
},
|
185 |
+
"language_info": {
|
186 |
+
"codemirror_mode": {
|
187 |
+
"name": "ipython",
|
188 |
+
"version": 3
|
189 |
+
},
|
190 |
+
"file_extension": ".py",
|
191 |
+
"mimetype": "text/x-python",
|
192 |
+
"name": "python",
|
193 |
+
"nbconvert_exporter": "python",
|
194 |
+
"pygments_lexer": "ipython3",
|
195 |
+
"version": "3.7.1"
|
196 |
+
},
|
197 |
+
"toc": {
|
198 |
+
"base_numbering": 1,
|
199 |
+
"nav_menu": {},
|
200 |
+
"number_sections": true,
|
201 |
+
"sideBar": true,
|
202 |
+
"skip_h1_title": false,
|
203 |
+
"title_cell": "Table of Contents",
|
204 |
+
"title_sidebar": "Contents",
|
205 |
+
"toc_cell": false,
|
206 |
+
"toc_position": {},
|
207 |
+
"toc_section_display": true,
|
208 |
+
"toc_window_display": false
|
209 |
+
}
|
210 |
+
},
|
211 |
+
"nbformat": 4,
|
212 |
+
"nbformat_minor": 2
|
213 |
+
}
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
datasets==1.11.0
|
2 |
+
transformers==4.9.2
|
3 |
+
torch
|
4 |
+
Pillow
|
5 |
+
requests
|
utils.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
45 |
+
text = f.read()
|
46 |
+
|
47 |
+
return text
|
48 |
+
|
49 |
+
|
50 |
+
def load_json(json_path):
|
51 |
+
jdata = ''
|
52 |
+
with open(json_path) as f:
|
53 |
+
jdata = json.load(f)
|
54 |
+
|
55 |
+
return jdata
|
56 |
+
|
57 |
+
|
58 |
+
def local_css(css_path):
|
59 |
+
with open(css_path) as f:
|
60 |
+
st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
|
61 |
+
|
62 |
+
|
63 |
+
def remote_css(css_url):
|
64 |
+
st.markdown(f'<link href="{css_url}" rel="stylesheet">', unsafe_allow_html=True)
|
65 |
+
|
66 |
+
|
67 |
+
def unique_list(seq):
|
68 |
+
seen = set()
|
69 |
+
seen_add = seen.add
|
70 |
+
return [x for x in seq if not (x in seen or seen_add(x))]
|
71 |
+
|
72 |
+
|
73 |
+
def pure_comma_separation(list_str, return_list=True):
|
74 |
+
r = unique_list([item.strip() for item in list_str.lower().split(",") if item.strip()])
|
75 |
+
# r = list(set([x.strip() for x in list_str.strip().split(',') if len(x.strip()) > 0]))
|
76 |
+
if return_list:
|
77 |
+
return r
|
78 |
+
return ", ".join(r)
|
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,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import (
|
2 |
+
Image,
|
3 |
+
ImageDraw
|
4 |
+
)
|
5 |
+
import textwrap
|
6 |
+
from utils.utils import load_image_from_url
|
7 |
+
from utils.ext import (
|
8 |
+
ingredients as ext_ingredients,
|
9 |
+
directions as ext_directions
|
10 |
+
)
|
11 |
+
|
12 |
+
|
13 |
+
# from .utils import load_image_from_url
|
14 |
+
# from .ext import (
|
15 |
+
# ingredients as ext_ingredients,
|
16 |
+
# directions as ext_directions
|
17 |
+
# )
|
18 |
+
|
19 |
+
|
20 |
+
def generate_food_with_logo_image(bg_path, logo_path, food_url, no_food="asset/frame/no_food.png"):
|
21 |
+
bg = Image.open(bg_path)
|
22 |
+
width, height = bg.size
|
23 |
+
|
24 |
+
logo = Image.open(logo_path)
|
25 |
+
logo_width, logo_height, logo_ratio, logo_rb, logo_mb = logo.size + (3, -20, 45)
|
26 |
+
logo_width, logo_height = (logo_width // logo_ratio, logo_height // logo_ratio)
|
27 |
+
logo = logo.resize((logo_width, logo_height))
|
28 |
+
|
29 |
+
food = load_image_from_url(food_url, rgba_mode=True, default_image=no_food)
|
30 |
+
|
31 |
+
food_width, food_height = (300, 300)
|
32 |
+
food = food.resize((food_width, food_height))
|
33 |
+
|
34 |
+
bg.paste(food, (0, 0), food)
|
35 |
+
bg.paste(logo, (width - logo_width - logo_rb, height - logo_height - logo_mb), logo)
|
36 |
+
|
37 |
+
return bg
|
38 |
+
|
39 |
+
|
40 |
+
def generate_recipe_image(
|
41 |
+
recipe_data,
|
42 |
+
bg_path,
|
43 |
+
food_logo_ia,
|
44 |
+
fonts,
|
45 |
+
bg_color="#ffffff"
|
46 |
+
):
|
47 |
+
bg = Image.open(bg_path)
|
48 |
+
bg.paste(food_logo_ia, (50, 50), food_logo_ia)
|
49 |
+
bg_color = Image.new("RGBA", bg.size, bg_color)
|
50 |
+
bg_color.paste(bg, mask=bg)
|
51 |
+
|
52 |
+
im_editable = ImageDraw.Draw(bg_color)
|
53 |
+
im_editable.text(
|
54 |
+
(418, 30),
|
55 |
+
textwrap.fill(recipe_data["title"], 15).replace(" \n", "\n"),
|
56 |
+
(61, 61, 70),
|
57 |
+
font=fonts["title"],
|
58 |
+
)
|
59 |
+
|
60 |
+
im_editable.text(
|
61 |
+
(100, 450),
|
62 |
+
"Ingredients",
|
63 |
+
(61, 61, 70),
|
64 |
+
font=fonts["body_bold"],
|
65 |
+
)
|
66 |
+
ingredients = recipe_data["ingredients"]
|
67 |
+
ingredients = ext_ingredients(ingredients, [], without_mapping=True)
|
68 |
+
ingredients = [textwrap.fill(item, 30).replace("\n", "\n ") for item in ingredients]
|
69 |
+
|
70 |
+
im_editable.text(
|
71 |
+
(50, 520),
|
72 |
+
"\n".join([f"- {item}" for item in ingredients]),
|
73 |
+
(61, 61, 70),
|
74 |
+
font=fonts["body"],
|
75 |
+
)
|
76 |
+
|
77 |
+
im_editable.text(
|
78 |
+
(700, 450),
|
79 |
+
"Directions",
|
80 |
+
(61, 61, 70),
|
81 |
+
font=fonts["body_bold"],
|
82 |
+
)
|
83 |
+
|
84 |
+
directions = recipe_data["directions"]
|
85 |
+
directions = ext_directions(directions)
|
86 |
+
directions = [textwrap.fill(item, 70).replace("\n", "\n ").capitalize() for item in directions]
|
87 |
+
|
88 |
+
im_editable.text(
|
89 |
+
(430, 520),
|
90 |
+
"\n".join([f"{i + 1}. {item}" for i, item in enumerate(directions)]).strip(),
|
91 |
+
(61, 61, 70),
|
92 |
+
font=fonts["body"],
|
93 |
+
)
|
94 |
+
return bg_color
|
utils/ext.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
from utils.utils import replace_regex
|
3 |
+
# from .utils import replace_regex
|
4 |
+
|
5 |
+
DEFAULT_MAP_DICT = {
|
6 |
+
" c ": " c. ",
|
7 |
+
", chopped": " (chopped)",
|
8 |
+
", crumbled": " (crumbled)",
|
9 |
+
", thawed": " (thawed)",
|
10 |
+
", melted": " (melted)",
|
11 |
+
}
|
12 |
+
|
13 |
+
|
14 |
+
def ingredient(text, map_dict):
|
15 |
+
if len(map_dict) > 0:
|
16 |
+
map_dict.update(**DEFAULT_MAP_DICT)
|
17 |
+
else:
|
18 |
+
map_dict = DEFAULT_MAP_DICT
|
19 |
+
|
20 |
+
text = replace_regex(text, map_dict)
|
21 |
+
text = re.sub(r"(\d)\s(\d\/\d)", r" \1+\2 ", text)
|
22 |
+
text = " ".join([word.strip() for word in text.split() if word.strip()])
|
23 |
+
return text
|
24 |
+
|
25 |
+
|
26 |
+
def ingredients(text_list, item_list, without_mapping=False):
|
27 |
+
map_dict = {
|
28 |
+
item: f'<span class="text-bold">{item}</span>' for item in list(map(lambda x: x.lower().strip(), item_list))
|
29 |
+
}
|
30 |
+
text_list = list(map(lambda x: x.lower(), text_list))
|
31 |
+
|
32 |
+
output = []
|
33 |
+
for text in text_list:
|
34 |
+
map_dict = map_dict if not without_mapping else {}
|
35 |
+
text = ingredient(text, map_dict)
|
36 |
+
output.append(text)
|
37 |
+
|
38 |
+
return output
|
39 |
+
|
40 |
+
|
41 |
+
def directions(text_list):
|
42 |
+
text_list = list(map(lambda x: x.lower().capitalize(), text_list))
|
43 |
+
|
44 |
+
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))
|