import streamlit as st import cv2 import numpy as np import re import os import pandas as pd from PIL import Image import time import requests from paddleocr import PaddleOCR # --- KONFIGURASI APLIKASI --- st.set_page_config( page_title="Nutri-Grade Label Detection", page_icon="🥗", layout="wide", initial_sidebar_state="collapsed" ) # Gunakan API Key langsung sesuai permintaan OPENROUTER_API_KEY = "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" OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1" # --- FUNGSI-FUNGSI UTAMA --- @st.cache_resource def initialize_ocr(): """Inisialisasi model PaddleOCR tanpa parameter GPU.""" try: return PaddleOCR(lang='en', use_angle_cls=True) except Exception as e: st.error(f"Gagal inisialisasi OCR: {e}") return None def parse_numeric_value(text: str) -> float: """Parse string menjadi float dengan menghapus karakter non-numerik.""" cleaned = re.sub(r"[^\d\.\-]", "", str(text)) if cleaned in ['', '.', '-']: return 0.0 try: return float(cleaned) except: return 0.0 def get_nutrition_advice(serving_size, sugar_norm, fat_norm, sugar_grade, fat_grade, final_grade): """Memanggil API OpenRouter untuk mendapatkan saran nutrisi.""" prompt = f""" Anda adalah ahli gizi dari Indonesia yang ramah. - Takaran Saji: {serving_size} g/ml - Gula (per 100): {sugar_norm:.2f} g (Grade {sugar_grade.replace('Grade ', '')}) - Lemak Jenuh (per 100): {fat_norm:.2f} g (Grade {fat_grade.replace('Grade ', '')}) - Grade Akhir: {final_grade.replace('Grade ', '')} Berikan saran nutrisi singkat 50-80 kata, fokus pada dampak kesehatan dan tips praktis. """ headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}", "Content-Type": "application/json"} payload = { "model": "mistralai/mistral-7b-instruct:free", "messages": [{"role": "user", "content": prompt}], "max_tokens": 250, "temperature": 0.7 } try: r = requests.post(f"{OPENROUTER_BASE_URL}/chat/completions", headers=headers, json=payload, timeout=30) r.raise_for_status() data = r.json() return data["choices"][0]["message"]["content"].strip() except Exception as e: return f"Error: {e}" def get_grade_from_value(value, thresholds): if value <= thresholds["A"]: return "Grade A" if value <= thresholds["B"]: return "Grade B" if value <= thresholds["C"]: return "Grade C" return "Grade D" def get_grade_color(grade): colors = { "Grade A": ("#2ecc71", "white"), "Grade B": ("#f1c40f", "black"), "Grade C": ("#e67e22", "white"), "Grade D": ("#e74c3c", "white") } return colors.get(grade, ("#bdc3c7", "black")) def reset_state(): for k in ['ocr_done', 'data', 'calculated', 'calc']: if k in st.session_state: del st.session_state[k] # --- UI APLIKASI --- # Inisialisasi OCR cr = initialize_ocr() if cr is None: st.error("Model OCR tidak tersedia.") st.stop() st.title("🥗 Nutri-Grade Detection & Grade Calculator") st.caption("Analisis gizi produk berdasarkan standar Nutri-Grade Singapura.") with st.expander("📋 Petunjuk Penggunaan"): st.markdown(""" 1. Upload gambar (JPG/PNG). 2. Klik "Analisis OCR". 3. Koreksi hasil. 4. Klik "Hitung Grade". """ ) # Langkah 1: Upload st.header("1. Upload Gambar") file = st.file_uploader("Pilih gambar tabel gizi", type=["jpg", "jpeg", "png"], on_change=reset_state) if file: arr = np.frombuffer(file.read(), np.uint8) img = cv2.imdecode(arr, cv2.IMREAD_COLOR) st.image(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), width=300) if st.button("Analisis OCR"): with st.spinner("Mendeteksi teks..."): res = cr.ocr(img) texts = [ln[1][0] for ln in (res[0] if res else [])] full = " ".join(texts).lower() patterns = { 'serving': r"(takaran saj[i|a]|serving size)[^\d]*(\d+\.?\d*)", 'sugar': r"(gula|sugar)[^\d]*(\d+\.?\d*)", 'fat': r"(lemak jenuh|saturated fat)[^\d]*(\d+\.?\d*)" } data = {} for k, p in patterns.items(): m = re.search(p, full) if m: data[k] = m.group(2) st.session_state.data = data st.session_state.ocr_done = True st.success("OCR selesai!") st.rerun() # Langkah 2: Koreksi & Hitung if st.session_state.get('ocr_done'): st.header("2. Koreksi & Hitung Grade") d = st.session_state.data with st.form("form2"): serving = st.text_input("Takaran Saji (g/ml)", value=d.get('serving', '100')) sugar = st.text_input("Gula (g)", value=d.get('sugar', '0')) fat = st.text_input("Lemak Jenuh (g)", value=d.get('fat', '0')) ok = st.form_submit_button("Hitung Grade") if ok: sv = parse_numeric_value(serving) sg = parse_numeric_value(sugar) fg = parse_numeric_value(fat) sp = (sg / sv) * 100 if sv > 0 else 0 fp = (fg / sv) * 100 if sv > 0 else 0 st.session_state.calc = {'sv': sv, 'sp': sp, 'fp': fp} st.session_state.calculated = True # Langkah 3: Tampilkan Hasil if st.session_state.get('calculated'): c = st.session_state.calc gs = get_grade_from_value(c['sp'], {"A": 1.0, "B": 5.0, "C": 10.0}) gf = get_grade_from_value(c['fp'], {"A": 0.7, "B": 1.2, "C": 2.8}) final_grade = max(gs, gf, key=lambda x: ['Grade A', 'Grade B', 'Grade C', 'Grade D'].index(x)) st.header("3. Hasil Grading") cols = st.columns(3) def show(col, title, value, unit, grade): bg, textc = get_grade_color(grade) col.markdown( f"
" f"{title}

{value:.2f} {unit}

{grade}

", unsafe_allow_html=True ) show(cols[0], "Gula", c['sp'], "g/100ml", gs) show(cols[1], "Lemak Jenuh", c['fp'], "g/100ml", gf) show(cols[2], "Grade Akhir", 0, "", final_grade) st.divider() st.header("4. Saran Nutrisi AI") with st.spinner("Meminta AI..."): advice = get_nutrition_advice(c['sv'], c['sp'], c['fp'], gs, gf, final_grade) st.info(advice) # Footer st.markdown("---") st.markdown("

Nutri-Grade App v2.1 © 2024

", unsafe_allow_html=True)