Glainez commited on
Commit
420fe76
·
verified ·
1 Parent(s): be47e84

Upload folder using huggingface_hub

Browse files
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # Create a non-root user (HuggingFace Docker Spaces requirement)
4
+ RUN useradd -m -u 1000 user
5
+ USER user
6
+ ENV PATH="/home/user/.local/bin:$PATH"
7
+
8
+ WORKDIR /app
9
+
10
+ # Copy requirements and install
11
+ COPY --chown=user:user requirements.txt .
12
+ RUN pip install --user --no-cache-dir -r requirements.txt
13
+
14
+ # Copy application files
15
+ COPY --chown=user:user . .
16
+
17
+ # Expose port 7860 for HuggingFace Spaces
18
+ EXPOSE 7860
19
+
20
+ # Run Streamlit on the specified port
21
+ CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"]
README.md DELETED
@@ -1,12 +0,0 @@
1
- ---
2
- title: S3poke
3
- emoji: 🔥
4
- colorFrom: gray
5
- colorTo: pink
6
- sdk: gradio
7
- sdk_version: 6.10.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import requests
4
+ import time
5
+ import textwrap
6
+ import os
7
+ import glob
8
+
9
+ API_BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:8000")
10
+ CLIENT_ID = os.environ.get("CLIENT_ID", "DEMO_CLIENT_001")
11
+ APP_PASSWORD = os.environ.get("APP_PASSWORD", "s3poke") # Imposta questa nei Segreti di HF Space
12
+
13
+ st.set_page_config(page_title="Rilevatore di Ingredienti", layout="wide")
14
+
15
+ # CSS moderno e professionale con il font Inter
16
+ st.markdown("""
17
+ <style>
18
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
19
+
20
+ html, body, [class*="css"] {
21
+ font-family: 'Inter', sans-serif;
22
+ }
23
+
24
+ .title-container {
25
+ padding-bottom: 1rem;
26
+ margin-bottom: 2rem;
27
+ border-bottom: 1px solid #eaeaea;
28
+ }
29
+
30
+ .prediction-box {
31
+ padding: 16px;
32
+ margin-bottom: 12px;
33
+ border-radius: 6px;
34
+ border-left: 4px solid #2563eb;
35
+ background-color: #f8fafc;
36
+ color: #1e293b;
37
+ animation: fadeIn 0.5s ease-out;
38
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
39
+ }
40
+
41
+ .prediction-title {
42
+ font-size: 1.1em;
43
+ font-weight: 600;
44
+ color: #0f172a;
45
+ }
46
+
47
+ .prediction-score {
48
+ float: right;
49
+ color: #2563eb;
50
+ font-weight: 600;
51
+ font-size: 1.1em;
52
+ }
53
+
54
+ .prediction-desc {
55
+ font-size: 0.9em;
56
+ color: #64748b;
57
+ margin-top: 4px;
58
+ display: block;
59
+ }
60
+
61
+ @keyframes fadeIn {
62
+ 0% { opacity: 0; transform: translateY(5px); }
63
+ 100% { opacity: 1; transform: translateY(0); }
64
+ }
65
+ </style>
66
+ """, unsafe_allow_html=True)
67
+
68
+ # Logica di autenticazione
69
+ def check_password():
70
+ if not APP_PASSWORD:
71
+ return True
72
+
73
+ if "password_correct" not in st.session_state:
74
+ st.session_state["password_correct"] = False
75
+
76
+ if not st.session_state["password_correct"]:
77
+ st.markdown('<div class="title-container"><h1>Accesso Rilevatore di Ingredienti</h1></div>', unsafe_allow_html=True)
78
+ st.info("Questo spazio è privato. Inserisci la password di accesso.")
79
+ password = st.text_input("Password", type="password")
80
+ if st.button("Accedi"):
81
+ if password == APP_PASSWORD:
82
+ st.session_state["password_correct"] = True
83
+ st.rerun()
84
+ else:
85
+ st.error("Password errata.")
86
+ return False
87
+ return True
88
+
89
+ if not check_password():
90
+ st.stop()
91
+
92
+
93
+ @st.cache_resource(show_spinner=False)
94
+ def configure_api():
95
+ config_dir = os.path.join(os.path.dirname(__file__), "csv_config")
96
+ csv_files = glob.glob(os.path.join(config_dir, "*.csv"))
97
+
98
+ if not csv_files:
99
+ return {"status": "error", "message": "Nessun CSV di configurazione trovato "}
100
+
101
+ try:
102
+ df = pd.read_csv(csv_files[0])
103
+ required_cols = {"PRODUCT_ID", "DESCRIPTION", "FAMILY", "COMPONENT", "EXTRA DESCRIPTION"}
104
+ if not required_cols.issubset(set(df.columns)):
105
+ return {"status": "error", "message": "Colonne richieste mancanti nel CSV."}
106
+
107
+ mask = (df["COMPONENT"].str.lower() == "ingredient") | (df["COMPONENT"].str.lower().str.startswith("proteine"))
108
+ df_filtered = df[mask].copy()
109
+
110
+ df_filtered["id"] = df_filtered["PRODUCT_ID"].astype(str)
111
+ df_filtered["description"] = df_filtered["DESCRIPTION"].astype(str)
112
+
113
+ df_filtered["FAMILY"] = df_filtered["FAMILY"].fillna("")
114
+ df_filtered["EXTRA DESCRIPTION"] = df_filtered["EXTRA DESCRIPTION"].fillna("")
115
+ df_filtered["extra_description"] = df_filtered["COMPONENT"].astype(str) + " - " + df_filtered["EXTRA DESCRIPTION"].astype(str)
116
+
117
+ products = df_filtered[["id", "description", "extra_description"]].to_dict(orient="records")
118
+
119
+ payload = {
120
+ "client_id": CLIENT_ID,
121
+ "context": "Rilevamento degli ingredienti all'interno delle Poke Bowl in Italia.",
122
+ "products": products
123
+ }
124
+
125
+ resp = requests.post(f"{API_BASE_URL}/configure", json=payload)
126
+ if resp.status_code == 200:
127
+ return {"status": "success", "message": "API configurata con successo."}
128
+ else:
129
+ return {"status": "error", "message": f"Errore API {resp.status_code}: {resp.text}"}
130
+ except Exception as e:
131
+ return {"status": "error", "message": f"Impossibile configurare l'API: {e}"}
132
+
133
+ # Inizializza la configurazione silenziosamente
134
+ with st.spinner("Inizializzazione configurazione API..."):
135
+ config_status = configure_api()
136
+
137
+ st.markdown('<div class="title-container"><h1>Rilevatore di Ingredienti</h1></div>', unsafe_allow_html=True)
138
+
139
+ if config_status["status"] == "error":
140
+ st.error(f"Errore di Inizializzazione: {config_status['message']}")
141
+ st.info("Controlla i file di configurazione e la connessione API.")
142
+
143
+ st.markdown("### Analisi dell'Immagine")
144
+ st.markdown("Carica l'immagine di una bowl o seleziona un'immagine di test per identificarne automaticamente gli ingredienti.")
145
+
146
+ # Cerca la cartella delle immagini di test (demo/test_images o ../tests)
147
+ possible_test_dirs = [
148
+ os.path.join(os.path.dirname(__file__), "test_images"),
149
+ os.path.join(os.path.dirname(__file__), "..", "tests")
150
+ ]
151
+ test_images_dir = next((d for d in possible_test_dirs if os.path.exists(d)), None)
152
+
153
+ image_to_analyze_path = None
154
+ uploaded_img = None
155
+
156
+ if test_images_dir:
157
+ test_images = []
158
+ for ext in ('*.png', '*.jpg', '*.jpeg', '*.webp'):
159
+ test_images.extend(glob.glob(os.path.join(test_images_dir, ext)))
160
+ test_images = sorted(test_images)
161
+
162
+ if test_images:
163
+ selection_mode = st.radio("Scegli la sorgente dell'immagine:", ["Carica la tua", "Seleziona un'immagine di test"], horizontal=True)
164
+ if selection_mode == "Seleziona un'immagine di test":
165
+ img_names = [os.path.basename(p) for p in test_images]
166
+ selected_img_name = st.selectbox("Seleziona un'immagine di test:", img_names)
167
+ image_to_analyze_path = os.path.join(test_images_dir, selected_img_name)
168
+ else:
169
+ uploaded_img = st.file_uploader("Carica Immagine", type=["png", "jpg", "jpeg", "webp"], label_visibility="collapsed")
170
+ else:
171
+ uploaded_img = st.file_uploader("Carica Immagine", type=["png", "jpg", "jpeg", "webp"], label_visibility="collapsed")
172
+
173
+ if uploaded_img is not None or image_to_analyze_path is not None:
174
+ col1, col2 = st.columns([1, 1], gap="large")
175
+
176
+ with col1:
177
+ if uploaded_img is not None:
178
+ st.image(uploaded_img, use_container_width=True)
179
+ img_name = uploaded_img.name
180
+ img_bytes = uploaded_img.getvalue()
181
+ img_type = uploaded_img.type
182
+ else:
183
+ with open(image_to_analyze_path, "rb") as f:
184
+ img_bytes = f.read()
185
+ st.image(img_bytes, use_container_width=True)
186
+ img_name = os.path.basename(image_to_analyze_path)
187
+ img_type = "image/jpeg" if img_name.lower().endswith(('.jpg', '.jpeg')) else "image/png"
188
+
189
+ with col2:
190
+ if st.button("Identifica Ingredienti", type="primary", use_container_width=True):
191
+ with st.spinner("Analisi dell'immagine in corso..."):
192
+ files = {"image": (img_name, img_bytes, img_type)}
193
+ data = {"client_id": CLIENT_ID}
194
+ try:
195
+ resp = requests.post(f"{API_BASE_URL}/predict", files=files, data=data)
196
+ if resp.status_code == 200:
197
+ predictions = resp.json().get("predictions", [])
198
+ if not predictions:
199
+ st.info("Nessun ingrediente rilevato nell'immagine.")
200
+ else:
201
+ st.success("Analisi completata")
202
+
203
+ result_container = st.empty()
204
+ displayed_markdown = "<h4>Componenti Rilevati</h4>"
205
+
206
+ for pred in predictions:
207
+ score_pct = pred.get("score", 0) * 100
208
+ desc = pred.get("description") or "Nessun dettaglio aggiuntivo disponibile"
209
+ p_id = pred.get("product_id")
210
+
211
+ item_html = textwrap.dedent(f"""
212
+ <div class="prediction-box">
213
+ <span class="prediction-title">{p_id}</span>
214
+ <span class="prediction-score">{score_pct:.1f}%</span><br>
215
+ <span class="prediction-desc">{desc}</span>
216
+ </div>
217
+ """)
218
+ displayed_markdown += item_html
219
+
220
+ result_container.markdown(displayed_markdown, unsafe_allow_html=True)
221
+ time.sleep(0.08) # Elegante ritardo d'animazione
222
+
223
+ else:
224
+ st.error(f"Errore API {resp.status_code}: {resp.text}")
225
+ except Exception as e:
226
+ st.error(f"Impossibile connettersi all'API: {e}")
csv_config/s3_I_LOVE_POKE_ingredients.csv ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ PRODUCT_ID,DESCRIPTION,FAMILY,PRICE,COMPONENT,EXTRA DESCRIPTION
2
+ kb01,riso nero,kiosk basi,0,base,black rice
3
+ kb02,riso bianco,kiosk basi,0,base,white rice
4
+ kb03,riso integrale ,kiosk basi,0,base,brown rice
5
+ kb04,cous cous ,kiosk basi,0,base,couscous
6
+ kb05,insalata,kiosk basi,0,base,salad
7
+ kb06,metà metà ,kiosk basi,0,base,half and half
8
+ kb07,insalata,kiosk basi,0,base,salad
9
+ kb08,no base,kiosk basi,0,base,no base
10
+ king01,avocado hass,kiosk ingredienti,0,ingredient,hass avocado
11
+ king02,guacamole,kiosk ingredienti,0,ingredient,guacamole
12
+ king03,alga wakame,kiosk ingredienti,0,ingredient,wakame seaweed
13
+ king04,cream cheese ,kiosk ingredienti,0,ingredient,cream cheese
14
+ king05,feta,kiosk ingredienti,0,ingredient,feta
15
+ king06,mango,kiosk ingredienti,0,ingredient,mango
16
+ king07,hummus,kiosk ingredienti,0,ingredient,hummus
17
+ king08,carciofi,kiosk ingredienti,0,ingredient,artichokes
18
+ king09,zucchine limone menta,kiosk ingredienti,0,ingredient,zucchini with lemon and mint
19
+ king10,olive nere,kiosk ingredienti,0,ingredient,black olives
20
+ king11,mais,kiosk ingredienti,0,ingredient,corn
21
+ king12,zenzero,kiosk ingredienti,0,ingredient,ginger
22
+ king13,cavolo rosso,kiosk ingredienti,0,ingredient,red cabbage
23
+ king14,carote,kiosk ingredienti,0,ingredient,carrots
24
+ king15,cipolla rossa,kiosk ingredienti,0,ingredient,red onion
25
+ king16,pomodorini,kiosk ingredienti,0,ingredient,cherry tomatoes
26
+ king17,cetrioli,kiosk ingredienti,0,ingredient,cucumbers
27
+ king18,edamame,kiosk ingredienti,0,ingredient,edamame
28
+ king19,ananas,kiosk ingredienti,0,ingredient,pineapple
29
+ kprf01,freddo - tartare salmone,kiosk ingredienti,0,proteine fredde. Crudo,cold - salmon tartare
30
+ kprf02,freddo - salmone al naturale,kiosk ingredienti,0,proteine fredde. Crudo,cold - plain salmon
31
+ kprf03,freddo - salmone marinato all'hawaiana,kiosk ingredienti,0,proteine fredde. Crudo,cold - Hawaiian marinated salmon
32
+ kprf04,freddo - tonno al naturale,kiosk ingredienti,0,proteine fredde. Crudo,cold - plain tuna
33
+ kprf05,freddo - gamberi al vapore,kiosk ingredienti,0,proteine fredde. Cotto,cold - steamed shrimp
34
+ kprf06,freddo - tentacoli e patate,kiosk ingredienti,0,proteine fredde. Cotto,cold - octopus tentacles and potatoes
35
+ kprf07,freddo - insalata di tonno,kiosk ingredienti,0,proteine fredde. Crudo,cold - tuna salad
36
+ kprc01,caldo - bocconcini di pollo,kiosk ingredienti,0,proteine calde. Crudo,hot - chicken bites
37
+ kprc02,caldo - salmone a vapore,kiosk ingredienti,0,proteine calde. Cotto,hot - steamed salmon
38
+ kprv01,vegetariano - polpette di soia,kiosk ingredienti,0,proteine vegetale,vegetarian - soy meatballs
39
+ kprv02,vegan - bocconcini planted a limone,kiosk ingredienti,0,proteine vegetale,vegan - lemon plant-based bites
40
+ ksa01,teriyaki ,kiosk ingredienti,0,salsa,teriyaki
41
+ ksa02,ponzu,kiosk ingredienti,0,salsa,ponzu
42
+ ksa03,soia senza glutine,kiosk ingredienti,0,salsa,gluten-free soy sauce
43
+ ksa04,sriracha,kiosk ingredienti,0,salsa,sriracha
44
+ ksa05,olio extra vergine di oliva,kiosk ingredienti,0,salsa,extra virgin olive oil
45
+ ksa06,vinaigrette,kiosk ingredienti,0,salsa,vinaigrette
46
+ ksa07,citronette,kiosk ingredienti,0,salsa,citronette
47
+ ksa08,no salsa,kiosk ingredienti,0,salsa,no sauce
48
+ kexsal01,hawaiian special,kiosk ingredienti,0,salsa extra,hawaiian special
49
+ kexsal02,maio vegana,kiosk ingredienti,0,salsa extra,vegan mayo
50
+ kexsal03,yogurt,kiosk ingredienti,0,salsa extra,yogurt
51
+ kexsal04,maio piccante ,kiosk ingredienti,0,salsa extra,spicy mayo
52
+ kexsal05,maio wasabi,kiosk ingredienti,0,salsa extra,wasabi mayo
53
+ kexsal06,agropiccante tropicale,kiosk ingredienti,0,salsa extra,tropical hot and sour
54
+ ktop01,granella di noci,kiosk ingredienti,0,topping,crushed walnuts
55
+ ktop02,patate crispy,kiosk ingredienti,0,topping,crispy potatoes
56
+ ktop03,mandorle,kiosk ingredienti,0,topping,almonds
57
+ ktop04,sesamo ,kiosk ingredienti,0,topping,sesame
58
+ ktop05,cipolla croccante,kiosk ingredienti,0,topping,crispy onion
59
+ ktop06,mais tostato,kiosk ingredienti,0,topping,toasted corn
60
+ ksd01,Edamame classico,kiosk ingredienti,3,side,classic edamame
61
+ ksd02,Edamame piccante,kiosk ingredienti,3,side,spicy edamame
62
+ ksd03,Edamame citronette,kiosk ingredienti,3,side,citronette edamame
63
+ ksd04,Edamame tartufo,kiosk ingredienti,4,side,truffle edamame
64
+ ksd05,Guacamole chips,kiosk ingredienti,4,side,guacamole & chips
65
+ ksd06,Alghe wakame,kiosk ingredienti,3,side,wakame seaweed
66
+ kd01,Hawaiian Macedonia ,kiosk ingredienti,3.5,dolce,Hawaiian fruit salad
67
+ kd02,Mix mango e ananas fresco,kiosk ingredienti,0,dolce,fresh mango and pineapple mix
68
+ kbv01,Acqua in brick 500ml,kiosk ingredienti,1.5,bevanda,boxed water 500ml
69
+ kbv02,Acqua frizzante 500ml PET,kiosk ingredienti,1.5,bevanda,sparkling water 500ml PET
70
+ kbv03,Coca-Cola 45cl PET,kiosk ingredienti,3,bevanda,Coca-Cola 45cl PET
71
+ kbv04,Coca-Cola zero 45cl PET,kiosk ingredienti,3,bevanda,Coca-Cola Zero 45cl PET
72
+ kbv05,Red Bull energy drink 355ml,kiosk ingredienti,3.5,bevanda,Red Bull energy drink 355ml
73
+ kbv06,Red Bull sugarfree 355ml,kiosk ingredienti,3.5,bevanda,Red Bull sugarfree 355ml
74
+ kbv07,Fuzetea limone 40cl,kiosk ingredienti,3,bevanda,lemon Fuzetea 40cl
75
+ kbv08,Fuzetea pesca 40cl,kiosk ingredienti,3,bevanda,peach Fuzetea 40cl
76
+ kbv09,Chinotto Lurisia 275ml,kiosk ingredienti,3,bevanda,Lurisia Chinotto 275ml
77
+ kbv10,Limonata Lurisia 275ml,kiosk ingredienti,3,bevanda,Lurisia Lemonade 275ml
78
+ kbv11,Gazzosa Lurisia 275ml,kiosk ingredienti,3,bevanda,Lurisia Gazzosa 275ml
79
+ kbr01,Maui Beach Lager 33cl,kiosk ingredienti,4,bevanda,Maui Beach Lager 33cl
80
+ kbr02,Sunset Ipa 33cl,kiosk ingredienti,4,bevanda,Sunset IPA 33cl
81
+ kbr03,Poretti 3 Luppoli 33cl,kiosk ingredienti,4,bevanda,Poretti 3 Luppoli 33cl
launcher.sh ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # This script builds and runs the Streamlit demo in a Docker container,
3
+ # mimicking the environment of a Hugging Face Space.
4
+
5
+ # Build the docker image
6
+ echo "Building the Docker image: ingredient-detector-demo..."
7
+ sudo docker build -t ingredient-detector-demo .
8
+
9
+ # Run the docker image
10
+ # Port 7860 is used by the Dockerfile (Hugging Face default)
11
+ echo "Starting the container on http://localhost:7860..."
12
+ sudo docker run -p 7860:7860 \
13
+ -e API_BASE_URL="https://u3wqf8ysjq.eu-central-1.awsapprunner.com" \
14
+ -e CLIENT_ID="DEMO_CLIENT_001" \
15
+ -e APP_PASSWORD="s3poke" \
16
+ ingredient-detector-demo
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ streamlit
2
+ pandas
3
+ requests
4
+ python-dotenv
test_images/poke_test1.jpeg ADDED
test_images/poke_test2.jpeg ADDED
test_images/poke_test3.jpeg ADDED
test_images/poke_test4.jpeg ADDED
test_images/poke_test5.jpeg ADDED
test_images/poke_test6.jpeg ADDED
test_images/poke_test7.jpeg ADDED