Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
@@ -28,18 +28,78 @@ OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
|
28 |
def initialize_ocr():
|
29 |
"""Inisialisasi model PaddleOCR tanpa parameter GPU."""
|
30 |
try:
|
31 |
-
# Hanya tentukan language dan angle classifier
|
32 |
return PaddleOCR(lang='en', use_angle_cls=True)
|
33 |
except Exception as e:
|
34 |
st.error(f"Gagal inisialisasi OCR: {e}")
|
35 |
return None
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
def reset_state():
|
38 |
-
for k in ['ocr_done','data','calculated']:
|
39 |
-
st.session_state
|
|
|
40 |
|
41 |
# --- UI APLIKASI ---
|
42 |
|
|
|
43 |
cr = initialize_ocr()
|
44 |
if cr is None:
|
45 |
st.error("Model OCR tidak tersedia.")
|
@@ -59,7 +119,7 @@ with st.expander("📋 Petunjuk Penggunaan"):
|
|
59 |
|
60 |
# Langkah 1: Upload
|
61 |
st.header("1. Upload Gambar")
|
62 |
-
file = st.file_uploader("Pilih gambar tabel gizi", type=["jpg","jpeg","png"], on_change=reset_state)
|
63 |
|
64 |
if file:
|
65 |
arr = np.frombuffer(file.read(), np.uint8)
|
@@ -68,7 +128,7 @@ if file:
|
|
68 |
|
69 |
if st.button("Analisis OCR"):
|
70 |
with st.spinner("Mendeteksi teks..."):
|
71 |
-
res = cr.ocr(img
|
72 |
texts = [ln[1][0] for ln in (res[0] if res else [])]
|
73 |
full = " ".join(texts).lower()
|
74 |
patterns = {
|
@@ -77,7 +137,7 @@ if file:
|
|
77 |
'fat': r"(lemak jenuh|saturated fat)[^\d]*(\d+\.?\d*)"
|
78 |
}
|
79 |
data = {}
|
80 |
-
for k,p in patterns.items():
|
81 |
m = re.search(p, full)
|
82 |
if m:
|
83 |
data[k] = m.group(2)
|
@@ -91,38 +151,43 @@ if st.session_state.get('ocr_done'):
|
|
91 |
st.header("2. Koreksi & Hitung Grade")
|
92 |
d = st.session_state.data
|
93 |
with st.form("form2"):
|
94 |
-
serving = st.text_input("Takaran Saji (g/ml)", value=d.get('serving','100'))
|
95 |
-
sugar = st.text_input("Gula (g)", value=d.get('sugar','0'))
|
96 |
-
fat = st.text_input("Lemak Jenuh (g)", value=d.get('fat','0'))
|
97 |
ok = st.form_submit_button("Hitung Grade")
|
98 |
if ok:
|
99 |
sv = parse_numeric_value(serving)
|
100 |
sg = parse_numeric_value(sugar)
|
101 |
fg = parse_numeric_value(fat)
|
102 |
-
sp = sg/sv*100 if sv>0 else 0
|
103 |
-
fp = fg/sv*100 if sv>0 else 0
|
104 |
-
st.session_state.calc = {'sv':sv,'sp':sp,'fp':fp}
|
105 |
st.session_state.calculated = True
|
106 |
|
107 |
# Langkah 3: Tampilkan Hasil
|
108 |
if st.session_state.get('calculated'):
|
109 |
c = st.session_state.calc
|
110 |
-
gs = get_grade_from_value(c['sp'],{"A":1.0,"B":5.0,"C":10.0})
|
111 |
-
gf = get_grade_from_value(c['fp'],{"A":0.7,"B":1.2,"C":2.8})
|
112 |
-
|
|
|
113 |
st.header("3. Hasil Grading")
|
114 |
cols = st.columns(3)
|
115 |
-
def show(col,title,
|
116 |
-
|
117 |
-
col.markdown(
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
|
|
|
|
|
|
|
|
122 |
st.divider()
|
123 |
st.header("4. Saran Nutrisi AI")
|
124 |
with st.spinner("Meminta AI..."):
|
125 |
-
advice = get_nutrition_advice(c['sv'],c['sp'],c['fp'],gs,gf,
|
126 |
st.info(advice)
|
127 |
|
128 |
# Footer
|
|
|
28 |
def initialize_ocr():
|
29 |
"""Inisialisasi model PaddleOCR tanpa parameter GPU."""
|
30 |
try:
|
|
|
31 |
return PaddleOCR(lang='en', use_angle_cls=True)
|
32 |
except Exception as e:
|
33 |
st.error(f"Gagal inisialisasi OCR: {e}")
|
34 |
return None
|
35 |
|
36 |
+
|
37 |
+
def parse_numeric_value(text: str) -> float:
|
38 |
+
"""Parse string menjadi float dengan menghapus karakter non-numerik."""
|
39 |
+
cleaned = re.sub(r"[^\d\.\-]", "", str(text))
|
40 |
+
if cleaned in ['', '.', '-']:
|
41 |
+
return 0.0
|
42 |
+
try:
|
43 |
+
return float(cleaned)
|
44 |
+
except:
|
45 |
+
return 0.0
|
46 |
+
|
47 |
+
|
48 |
+
def get_nutrition_advice(serving_size, sugar_norm, fat_norm, sugar_grade, fat_grade, final_grade):
|
49 |
+
"""Memanggil API OpenRouter untuk mendapatkan saran nutrisi."""
|
50 |
+
prompt = f"""
|
51 |
+
Anda adalah ahli gizi dari Indonesia yang ramah.
|
52 |
+
- Takaran Saji: {serving_size} g/ml
|
53 |
+
- Gula (per 100): {sugar_norm:.2f} g (Grade {sugar_grade.replace('Grade ', '')})
|
54 |
+
- Lemak Jenuh (per 100): {fat_norm:.2f} g (Grade {fat_grade.replace('Grade ', '')})
|
55 |
+
- Grade Akhir: {final_grade.replace('Grade ', '')}
|
56 |
+
|
57 |
+
Berikan saran nutrisi singkat 50-80 kata, fokus pada dampak kesehatan dan tips praktis.
|
58 |
+
"""
|
59 |
+
headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}", "Content-Type": "application/json"}
|
60 |
+
payload = {
|
61 |
+
"model": "mistralai/mistral-7b-instruct:free",
|
62 |
+
"messages": [{"role": "user", "content": prompt}],
|
63 |
+
"max_tokens": 250,
|
64 |
+
"temperature": 0.7
|
65 |
+
}
|
66 |
+
try:
|
67 |
+
r = requests.post(f"{OPENROUTER_BASE_URL}/chat/completions", headers=headers, json=payload, timeout=30)
|
68 |
+
r.raise_for_status()
|
69 |
+
data = r.json()
|
70 |
+
return data["choices"][0]["message"]["content"].strip()
|
71 |
+
except Exception as e:
|
72 |
+
return f"Error: {e}"
|
73 |
+
|
74 |
+
|
75 |
+
def get_grade_from_value(value, thresholds):
|
76 |
+
if value <= thresholds["A"]:
|
77 |
+
return "Grade A"
|
78 |
+
if value <= thresholds["B"]:
|
79 |
+
return "Grade B"
|
80 |
+
if value <= thresholds["C"]:
|
81 |
+
return "Grade C"
|
82 |
+
return "Grade D"
|
83 |
+
|
84 |
+
|
85 |
+
def get_grade_color(grade):
|
86 |
+
colors = {
|
87 |
+
"Grade A": ("#2ecc71", "white"),
|
88 |
+
"Grade B": ("#f1c40f", "black"),
|
89 |
+
"Grade C": ("#e67e22", "white"),
|
90 |
+
"Grade D": ("#e74c3c", "white")
|
91 |
+
}
|
92 |
+
return colors.get(grade, ("#bdc3c7", "black"))
|
93 |
+
|
94 |
+
|
95 |
def reset_state():
|
96 |
+
for k in ['ocr_done', 'data', 'calculated', 'calc']:
|
97 |
+
if k in st.session_state:
|
98 |
+
del st.session_state[k]
|
99 |
|
100 |
# --- UI APLIKASI ---
|
101 |
|
102 |
+
# Inisialisasi OCR
|
103 |
cr = initialize_ocr()
|
104 |
if cr is None:
|
105 |
st.error("Model OCR tidak tersedia.")
|
|
|
119 |
|
120 |
# Langkah 1: Upload
|
121 |
st.header("1. Upload Gambar")
|
122 |
+
file = st.file_uploader("Pilih gambar tabel gizi", type=["jpg", "jpeg", "png"], on_change=reset_state)
|
123 |
|
124 |
if file:
|
125 |
arr = np.frombuffer(file.read(), np.uint8)
|
|
|
128 |
|
129 |
if st.button("Analisis OCR"):
|
130 |
with st.spinner("Mendeteksi teks..."):
|
131 |
+
res = cr.ocr(img)
|
132 |
texts = [ln[1][0] for ln in (res[0] if res else [])]
|
133 |
full = " ".join(texts).lower()
|
134 |
patterns = {
|
|
|
137 |
'fat': r"(lemak jenuh|saturated fat)[^\d]*(\d+\.?\d*)"
|
138 |
}
|
139 |
data = {}
|
140 |
+
for k, p in patterns.items():
|
141 |
m = re.search(p, full)
|
142 |
if m:
|
143 |
data[k] = m.group(2)
|
|
|
151 |
st.header("2. Koreksi & Hitung Grade")
|
152 |
d = st.session_state.data
|
153 |
with st.form("form2"):
|
154 |
+
serving = st.text_input("Takaran Saji (g/ml)", value=d.get('serving', '100'))
|
155 |
+
sugar = st.text_input("Gula (g)", value=d.get('sugar', '0'))
|
156 |
+
fat = st.text_input("Lemak Jenuh (g)", value=d.get('fat', '0'))
|
157 |
ok = st.form_submit_button("Hitung Grade")
|
158 |
if ok:
|
159 |
sv = parse_numeric_value(serving)
|
160 |
sg = parse_numeric_value(sugar)
|
161 |
fg = parse_numeric_value(fat)
|
162 |
+
sp = (sg / sv) * 100 if sv > 0 else 0
|
163 |
+
fp = (fg / sv) * 100 if sv > 0 else 0
|
164 |
+
st.session_state.calc = {'sv': sv, 'sp': sp, 'fp': fp}
|
165 |
st.session_state.calculated = True
|
166 |
|
167 |
# Langkah 3: Tampilkan Hasil
|
168 |
if st.session_state.get('calculated'):
|
169 |
c = st.session_state.calc
|
170 |
+
gs = get_grade_from_value(c['sp'], {"A": 1.0, "B": 5.0, "C": 10.0})
|
171 |
+
gf = get_grade_from_value(c['fp'], {"A": 0.7, "B": 1.2, "C": 2.8})
|
172 |
+
final_grade = max(gs, gf, key=lambda x: ['Grade A', 'Grade B', 'Grade C', 'Grade D'].index(x))
|
173 |
+
|
174 |
st.header("3. Hasil Grading")
|
175 |
cols = st.columns(3)
|
176 |
+
def show(col, title, value, unit, grade):
|
177 |
+
bg, textc = get_grade_color(grade)
|
178 |
+
col.markdown(
|
179 |
+
f"<div style='background:{bg};padding:10px;border-radius:8px;text-align:center;color:{textc};'>"
|
180 |
+
f"<strong>{title}</strong><p>{value:.2f} {unit}</p><h4>{grade}</h4></div>",
|
181 |
+
unsafe_allow_html=True
|
182 |
+
)
|
183 |
+
show(cols[0], "Gula", c['sp'], "g/100ml", gs)
|
184 |
+
show(cols[1], "Lemak Jenuh", c['fp'], "g/100ml", gf)
|
185 |
+
show(cols[2], "Grade Akhir", 0, "", final_grade)
|
186 |
+
|
187 |
st.divider()
|
188 |
st.header("4. Saran Nutrisi AI")
|
189 |
with st.spinner("Meminta AI..."):
|
190 |
+
advice = get_nutrition_advice(c['sv'], c['sp'], c['fp'], gs, gf, final_grade)
|
191 |
st.info(advice)
|
192 |
|
193 |
# Footer
|