BudgetCheck / app.py
rlgondong's picture
Update app.py
8e91210 verified
import gradio as gr
import torch
from torchvision import transforms
from PIL import Image
import joblib
import base64
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
import os
import tempfile
# Load model and metadata
try:
model = torch.jit.load("resnet_grocery_model_scripted.pt", map_location="cpu")
model.eval()
except Exception as e:
print(f"Error loading model: {str(e)}")
raise
try:
assets = joblib.load("deployment_assets.joblib")
transform = assets['transform']
class_names = assets['class_names']
print(f"Loaded {len(class_names)} classes")
except Exception as e:
print(f"Error loading assets: {str(e)}")
raise
# Price list
items = [
'Bisconni Chocolate Chip Cookies 46.8gm', 'Coca Cola Can 250ml', 'Colgate Maximum Cavity Protection 75gm',
'Fanta 500ml', 'Fresher Guava Nectar 500ml', 'Fruita Vitals Red Grapes 200ml', 'Islamabad Tea 238gm',
'Kolson Slanty Jalapeno 18gm', 'Kurkure Chutney Chaska 62gm', 'LU Candi Biscuit 60gm', 'LU Oreo Biscuit 19gm',
'LU Prince Biscuit 55.2gm', 'Lays Masala 34gm', 'Lays Wavy Mexican Chili 34gm', 'Lifebuoy Total Protect Soap 96gm',
'Lipton Yellow Label Tea 95gm', 'Meezan Ultra Rich Tea 190gm', 'Peek Freans Sooper Biscuit 13.2gm',
'Safeguard Bar Soap Pure White 175gm', 'Shezan Apple 250ml', 'Sunsilk Shampoo Soft - Smooth 160ml',
'Super Crisp BBQ 30gm', 'Supreme Tea 95gm', 'Tapal Danedar 95gm', 'Vaseline Healthy White Lotion 100ml'
]
prices = [
55.20, 31.75, 90.00, 63.50, 50.00, 35.00, 150.00, 15.00, 25.00, 30.00, 10.00, 30.00, 20.00, 20.00, 44.50,
100.00, 200.00, 10.00, 70.00, 25.00, 120.00, 15.00, 100.00, 100.00, 120.00
]
pricelist = dict(zip(items, prices))
def classify_and_track(files, budget):
results = []
total_cost = 0
receipt_lines = []
receipt_lines.append("========================================")
receipt_lines.append(" 🧾 OFFICIAL RECEIPT")
receipt_lines.append(" 🛒 SOBRANG TINDANG INA!")
receipt_lines.append(" 📍 Online Smart Checkout")
receipt_lines.append("========================================")
receipt_lines.append(" ITEM PRICE")
receipt_lines.append("----------------------------------------")
for f in files:
try:
print(f"Processing file: {f.name}, Exists: {os.path.exists(f.name)}")
img = Image.open(f.name).convert("RGB")
except Exception as e:
print(f"Image loading error for {f.name}: {str(e)}")
results.append((None, f"Invalid image: {str(e)}"))
continue
try:
input_tensor = transform(img).unsqueeze(0)
print(f"Input tensor shape: {input_tensor.shape}")
with torch.no_grad():
outputs = model(input_tensor)
print(f"Model output shape: {outputs.shape}")
predicted_index = torch.argmax(outputs, dim=1).item()
if predicted_index >= len(class_names):
raise ValueError(f"Predicted index {predicted_index} out of range for class_names (len={len(class_names)})")
predicted_class = class_names[predicted_index]
price = pricelist.get(predicted_class, 0)
total_cost += price
results.append((img, f"{predicted_class} - ₱{price:.2f}"))
name = predicted_class
if len(name) > 26:
name = name[:23] + "..."
receipt_lines.append(f" {name:<28}{price:>6.2f}")
except Exception as e:
print(f"Inference error for {f.name}: {str(e)}")
results.append((None, f"Inference error: {str(e)}"))
continue
remaining = budget - total_cost
receipt_lines.append("----------------------------------------")
receipt_lines.append(f" TOTAL{'':<24}{total_cost:>7.2f}")
receipt_lines.append(f" BUDGET{'':<23}{budget:>7.2f}")
receipt_lines.append(f" REMAINING{'':<20}{remaining:>7.2f}")
receipt_lines.append("========================================")
percent = min(100, int((total_cost / budget) * 100)) if budget else 0
status = f"**Total: ₱{total_cost:.2f} / ₱{budget:.2f}**"
if total_cost <= budget:
receipt_lines.append(" ✅ You're within budget! 🎉")
status_msg = f"✅ **Within Budget!** 🎉\n{status}"
sound_path = "success.mp3"
else:
receipt_lines.append(" ❌ Over budget! Remove some snacks!")
status_msg = f"🚨 **Over Budget!** 💸\n{status}"
sound_path = "fail.mp3"
receipt_lines.append("========================================")
receipt_lines.append(" THANK YOU FOR SHOPPING WITH US!")
receipt_lines.append(" Come back for smarter buys! 🧾")
receipt_lines.append("========================================")
# Convert receipt to markdown
receipt_text = "```\n" + "\n".join(receipt_lines) + "\n```"
# Generate PDF
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_pdf:
c = canvas.Canvas(tmp_pdf.name, pagesize=letter)
width, height = letter
y = height - 40
for line in receipt_lines:
c.drawString(40, y, line)
y -= 15
c.save()
pdf_path = tmp_pdf.name
return results, status_msg, receipt_text, percent, sound_path, gr.update(value=pdf_path, visible=True), gr.update(value=receipt_text, visible=True)
def reset_ui():
return [], "", "", 0, None, gr.update(visible=False), gr.update(visible=False)
# Load and encode local background image
try:
with open("background.jpg", "rb") as img_file:
b64_string = base64.b64encode(img_file.read()).decode()
except Exception as e:
print(f"Error loading background image: {str(e)}")
b64_string = ""
html_style = f"""
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;800&display=swap" rel="stylesheet">
<style>
body {{
background-image: url("data:image/jpg;base64,{b64_string}");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
font-family: 'Poppins', sans-serif;
color: #222;
}}
.glass-card {{
background: rgba(255, 255, 255, 0.75);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1.5px solid rgba(255, 255, 255, 0.2);
padding: 2rem;
margin-bottom: 1.5rem;
}}
h1 {{
text-align: center;
font-size: 2.6rem;
font-weight: 800;
color: #000 !important;
margin-bottom: 0.5rem;
font-family: 'Poppins', sans-serif;
}}
h2 {{
text-align: center;
font-size: 0.1rem;
font-weight: 0.5;
color: #000 !important;
font-family: 'Poppins', sans-serif;
}}
.gr-button {{
font-family: 'Poppins', sans-serif;
font-weight: 600;
}}
.gr-number input, .gr-file input {{
font-family: 'Poppins', sans-serif;
}}
</style>
"""
with gr.Blocks(title="Grocery Classifier") as app:
gr.HTML(html_style)
with gr.Column(elem_classes="glass-card"):
gr.Markdown("<h1>🧾 Budget Check! Know Before You Chow 🥗</h1>")
gr.Markdown("<h2>Snap a snack, upload the pack, and find out if you’re on track — or flat broke. Shopping has never been so dramatic.</h2>")
with gr.Column(elem_classes="glass-card"):
with gr.Row():
file_input = gr.File(file_types=["image"], file_count="multiple", label="📷 Upload Grocery Items")
budget_input = gr.Number(label="💰 Budget (₱)", value=500.0, precision=2)
with gr.Row():
classify_btn = gr.Button("🚀 Classify Items")
reset_btn = gr.Button("🔄 Reset")
with gr.Column(elem_classes="glass-card"):
gallery = gr.Gallery(label="📸 Classified Items", columns=3)
status_display = gr.Markdown()
progress_bar = gr.Slider(label="Budget Used (%)", minimum=0, maximum=100, interactive=False)
receipt_display = gr.Markdown(label="🧾 Grocery Receipt", visible=False)
download_receipt = gr.File(label="📥 Download Receipt PDF", interactive=False, visible=False)
audio_output = gr.Audio(interactive=False, autoplay=True, visible=False)
classify_btn.click(
fn=classify_and_track,
inputs=[file_input, budget_input],
outputs=[gallery, status_display, receipt_display, progress_bar, audio_output, download_receipt, receipt_display]
)
reset_btn.click(
fn=reset_ui,
outputs=[gallery, status_display, receipt_display, progress_bar, audio_output, download_receipt, receipt_display]
)
app.launch()