Spaces:
Runtime error
Runtime error
File size: 6,916 Bytes
f6e45d4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | # ==============================================================================
# EATHVISION API SERVER (Production Version)
# Description: High-performance FastAPI server running native YOLO (.pt) models.
# Integrates dual USDA databases for macros and ingredients.
# ==============================================================================
import os
import io
import pandas as pd
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from fastapi.responses import JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from ultralytics import YOLO
from PIL import Image
import warnings
warnings.filterwarnings('ignore')
# --- 1. Server Configuration ---
app = FastAPI(
title="eath API",
description="Production endpoint for dish classification and advanced nutrition tracking.",
version="1.0.0"
)
# Enable CORS for external frontend requests if needed
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Mount the static directory to serve HTML, CSS, JS, and Images
app.mount("/static", StaticFiles(directory="static"), name="static")
# --- 2. Global Variables & Resource Loading ---
MODELS_DIR = "models"
DATA_DIR = "data"
# File paths for both databases
MACROS_CSV_PATH = os.path.join(DATA_DIR, "Dishes Information and DV values V1.csv")
INGREDIENTS_CSV_PATH = os.path.join(DATA_DIR, "Nutrition Ingredient Database- Version 1.csv")
loaded_models = {}
macros_db = None
ingredients_db = None
def load_resources():
"""
Loads YOLO .pt models into memory and parses both nutrition CSVs.
"""
global loaded_models, macros_db, ingredients_db
print("[INIT] Loading AI Models into memory...")
# Load YOLO-X (High Precision)
yolo_x_path = os.path.join(MODELS_DIR, "best_yolo_x.pt")
if os.path.exists(yolo_x_path):
loaded_models["yolo_x"] = YOLO(yolo_x_path, task="classify")
print(" -> YOLO-X Loaded Successfully.")
# Load YOLO-S (High Speed)
yolo_s_path = os.path.join(MODELS_DIR, "best_yolo_s.pt")
if os.path.exists(yolo_s_path):
loaded_models["yolo_s"] = YOLO(yolo_s_path, task="classify")
print(" -> YOLO-S Loaded Successfully.")
# Load Databases
print("[INIT] Loading Databases...")
try:
macros_db = pd.read_csv(MACROS_CSV_PATH)
macros_db.columns = macros_db.columns.str.strip()
print(f" -> Macros DB Loaded: {len(macros_db)} entries.")
except Exception as e:
print(f"[ERROR] Failed to load Macros CSV: {e}")
try:
ingredients_db = pd.read_csv(INGREDIENTS_CSV_PATH)
ingredients_db.columns = ingredients_db.columns.str.strip()
print(f" -> Ingredients DB Loaded: {len(ingredients_db)} entries.")
except Exception as e:
print(f"[ERROR] Failed to load Ingredients CSV: {e}")
# Initialize resources on startup
load_resources()
# --- 3. API Endpoints ---
@app.get("/")
async def serve_frontend():
"""Serves the main HTML interface from the root URL."""
return FileResponse("static/index.html")
@app.post("/api/predict")
async def predict_dish(
file: UploadFile = File(...),
model_type: str = Form("yolo_x"),
portion_g: int = Form(100)
):
"""
Core prediction endpoint.
Returns AI prediction, calculated macros, and ingredient list.
"""
if model_type not in loaded_models:
raise HTTPException(status_code=400, detail=f"Model '{model_type}' is currently unavailable.")
# Read and convert image
try:
image_bytes = await file.read()
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
except Exception:
raise HTTPException(status_code=400, detail="Invalid image file format.")
# 1. AI Inference
try:
model = loaded_models[model_type]
results = model(image, imgsz=320, verbose=False)
top1_idx = results[0].probs.top1
raw_class_name = results[0].names[top1_idx]
confidence = float(results[0].probs.top1conf)
formatted_dish_name = raw_class_name.replace('_', ' ').title()
except Exception as e:
raise HTTPException(status_code=500, detail=f"Inference failed: {str(e)}")
# 2. Extract Macros
nutrition_data = {"status": "not_found", "message": "Macros not found."}
if macros_db is not None:
# Assuming the column is 'FoodName' or similar. Adjust if your CSV differs.
# Using a flexible search to avoid exact match case issues
match = macros_db[macros_db.iloc[:, 0].astype(str).str.contains(formatted_dish_name, case=False, na=False)]
if not match.empty:
base_data = match.iloc[0]
multiplier = portion_g / 100.0
def safe_calc(col_name):
try:
# Fetch by exact name if exists, else return 0
if col_name in base_data:
val = float(base_data[col_name])
else:
val = 0.0
return round(val * multiplier, 1)
except:
return 0.0
nutrition_data = {
"status": "success",
"portion_g": portion_g,
"base_data": {
"energy_kcal": safe_calc("Energy_kcal"),
"protein_g": safe_calc("Protein_g"),
"fat_g": safe_calc("Fat_g"),
"carbs_g": safe_calc("Carbs_g")
}
}
# 3. Extract Ingredients
ingredients_list = []
if ingredients_db is not None:
# Assuming first column is the dish name and the second contains ingredients
ing_match = ingredients_db[ingredients_db.iloc[:, 0].astype(str).str.contains(formatted_dish_name, case=False, na=False)]
if not ing_match.empty:
# Convert the matched row's ingredient column to a string
raw_ingredients = str(ing_match.iloc[0, 1])
# Split by comma to create a clean list for the frontend
ingredients_list = [ing.strip() for ing in raw_ingredients.split(',') if ing.strip()]
# 4. Final Response Payload
return JSONResponse(content={
"ai_prediction": {
"model_used": model_type,
"dish_name": formatted_dish_name,
"confidence": round(confidence * 100, 2)
},
"nutrition_insights": nutrition_data,
"ingredients": ingredients_list
})
if __name__ == "__main__":
import uvicorn
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True) |