har1zarD commited on
Commit
ef1a0b5
Β·
1 Parent(s): aafd758

app adjust

Browse files
Files changed (3) hide show
  1. README.md +124 -60
  2. app.py +220 -658
  3. requirements.txt +13 -28
README.md CHANGED
@@ -1,66 +1,135 @@
1
  ---
2
- title: AI Food Scanner
3
  emoji: 🍽️
4
  colorFrom: yellow
5
  colorTo: red
6
- sdk: gradio
7
- sdk_version: 4.44.0
8
- app_file: app.py
9
  pinned: false
10
  license: mit
11
  tags:
12
  - food-recognition
13
  - computer-vision
14
  - nutrition
15
- - ai
16
  - food-101
17
- - efficientnet
18
- - gradio
19
  ---
20
 
21
- # 🍽️ AI Food Scanner
22
 
23
- **Automatic food recognition powered by AI** - Upload an image and get instant food identification with nutritional information!
24
 
25
  ## 🎯 Features
26
 
27
- - πŸ€– **AI-Powered Recognition** - EfficientNet-B0 trained on Food-101 dataset
28
- - πŸ“Š **101 Food Categories** - From pizza to sushi, desserts to salads
29
- - πŸ₯— **Nutritional Information** - Calories, protein, carbs, and fat per 100g
30
- - πŸ“Έ **Image Quality Analysis** - Automatic assessment of upload quality
31
- - ⚑ **Fast Inference** - <0.5s on GPU, ~2s on CPU
32
- - 🎨 **Modern UI** - Clean Gradio interface with intuitive controls
33
 
34
- ## πŸš€ How to Use
35
 
36
- 1. **Upload an image** - Drag & drop or click to select
37
- 2. **Click "Analyze Food"** - AI processes your image
38
- 3. **View Results:**
39
- - Primary food match with confidence score
40
- - Top 5 alternative predictions
41
- - Nutritional breakdown (per 100g)
42
- - Image quality metrics
43
- - Model information
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  ## πŸ“Š Supported Categories
46
 
47
  The model recognizes **101 food categories** including:
48
 
49
- - **Main Courses:** Pizza, Sushi, Ramen, Steak, Hamburger, etc.
50
- - **Desserts:** Cheesecake, Ice Cream, Tiramisu, Donuts, etc.
51
  - **Salads:** Caesar Salad, Greek Salad, Caprese Salad, etc.
52
- - **Appetizers:** Falafel, Hummus, Spring Rolls, Bruschetta, etc.
53
- - **Fast Food:** French Fries, Hot Dogs, Nachos, Burgers, etc.
54
 
55
- [See full category list β†’](https://github.com/stratospark/food-101)
56
 
57
  ## πŸ”¬ Technical Details
58
 
59
  ### Model
60
- - **Architecture:** EfficientNet-B0
61
- - **Training Dataset:** Food-101 (101,000 images across 101 categories)
62
- - **Accuracy:** ~85-90% on validation set
63
- - **Framework:** PyTorch + Hugging Face Transformers
64
 
65
  ### Performance
66
  | Device | Inference Time |
@@ -69,38 +138,36 @@ The model recognizes **101 food categories** including:
69
  | CPU (4 cores) | ~2-3s |
70
 
71
  ### Stack
72
- - **Backend:** Python 3.11
73
- - **UI:** Gradio 4.0
74
- - **Deep Learning:** PyTorch 2.0+
75
- - **Deployment:** Hugging Face Spaces
76
 
77
  ## πŸ’‘ Tips for Best Results
78
 
79
- ### βœ… Good Images:
80
  - Well-lit, focused photos
81
  - Food fills most of the frame
82
- - Clear, unobstructed view
83
- - Single dish per image
84
 
85
- ### ❌ Avoid:
86
- - Dark, blurry, or low-quality images
87
- - Multiple different foods in one image
88
- - Extreme close-ups
89
- - Images smaller than 200x200px
90
 
91
- ## πŸ”§ Local Development
92
 
93
- ### Requirements
94
  ```bash
 
95
  pip install -r requirements.txt
96
- ```
97
 
98
- ### Run
99
- ```bash
100
  python app.py
101
- ```
102
 
103
- Then open http://localhost:7860 in your browser.
 
 
104
 
105
  ## πŸ“ License
106
 
@@ -110,17 +177,14 @@ Then open http://localhost:7860 in your browser.
110
 
111
  ## ⚠️ Disclaimer
112
 
113
- Nutritional information is estimated based on typical values for each food category. For precise nutritional data, consult product packaging or a registered dietitian.
114
 
115
  ## 🀝 Credits
116
 
117
- - Model: [Kaludi/food-category-classification-v2.0](https://huggingface.co/Kaludi/food-category-classification-v2.0)
118
- - Dataset: [Food-101](https://data.vision.ee.ethz.ch/cvl/datasets_extra/food-101/)
119
- - Framework: [Hugging Face Transformers](https://huggingface.co/transformers)
120
- - UI: [Gradio](https://gradio.app)
121
 
122
  ---
123
 
124
- **Made with ❀️ using PyTorch, Transformers, and Gradio**
125
-
126
- [Report an issue](https://github.com/YOUR_USERNAME/YOUR_REPO/issues) | [View source code](https://github.com/YOUR_USERNAME/YOUR_REPO)
 
1
  ---
2
+ title: Food Recognition API
3
  emoji: 🍽️
4
  colorFrom: yellow
5
  colorTo: red
6
+ sdk: docker
7
+ app_port: 7860
 
8
  pinned: false
9
  license: mit
10
  tags:
11
  - food-recognition
12
  - computer-vision
13
  - nutrition
14
+ - fastapi
15
  - food-101
16
+ - pytorch
 
17
  ---
18
 
19
+ # 🍽️ Food Recognition API
20
 
21
+ **FastAPI backend for AI-powered food recognition** - Accurate classification of 101 food categories with nutritional information.
22
 
23
  ## 🎯 Features
24
 
25
+ - πŸ€– **Food-101 Model** - Pre-trained on 101,000 images
26
+ - πŸ“Š **101 Food Categories** - Pizza, Sushi, Steak, and more
27
+ - πŸ₯— **Nutritional Data** - Calories, protein, carbs, fat
28
+ - ⚑ **Fast API** - RESTful endpoint with CORS
29
+ - πŸ”₯ **High Accuracy** - ~85% on Food-101 test set
30
+ - 🌐 **Next.js Ready** - Easy integration with frontend
31
 
32
+ ## πŸš€ API Endpoint
33
 
34
+ ### POST `/api/analyze-food`
35
+
36
+ Analyze a food image and get classification results.
37
+
38
+ **Request:**
39
+ ```bash
40
+ curl -X POST "https://huggingface.co/spaces/YOUR_USERNAME/foodrecognitionapi/api/analyze-food" \
41
+ -F "file=@pizza.jpg"
42
+ ```
43
+
44
+ **Response:**
45
+ ```json
46
+ {
47
+ "success": true,
48
+ "primary_prediction": {
49
+ "label": "pizza",
50
+ "name": "Pizza",
51
+ "confidence": 0.94
52
+ },
53
+ "top_predictions": [
54
+ {"label": "pizza", "name": "Pizza", "confidence": 0.94},
55
+ {"label": "lasagna", "name": "Lasagna", "confidence": 0.03},
56
+ ...
57
+ ],
58
+ "nutrition": {
59
+ "food_name": "Pizza",
60
+ "calories": 266,
61
+ "protein": 11,
62
+ "carbs": 33,
63
+ "fat": 10
64
+ },
65
+ "model_info": {
66
+ "model": "nateraw/food",
67
+ "dataset": "Food-101",
68
+ "num_classes": 101,
69
+ "device": "CPU"
70
+ }
71
+ }
72
+ ```
73
+
74
+ ## πŸ“– Other Endpoints
75
+
76
+ - **GET `/`** - API info
77
+ - **GET `/health`** - Health check
78
+ - **GET `/docs`** - Interactive API documentation (Swagger)
79
+
80
+ ## πŸ”§ Next.js Integration
81
+
82
+ ```typescript
83
+ // app/api/analyze-food/route.ts
84
+ export async function POST(request: Request) {
85
+ const formData = await request.formData();
86
+
87
+ const response = await fetch(
88
+ 'https://huggingface.co/spaces/YOUR_USERNAME/foodrecognitionapi/api/analyze-food',
89
+ {
90
+ method: 'POST',
91
+ body: formData,
92
+ }
93
+ );
94
+
95
+ return Response.json(await response.json());
96
+ }
97
+ ```
98
+
99
+ ```typescript
100
+ // Frontend usage
101
+ const analyzeFood = async (file: File) => {
102
+ const formData = new FormData();
103
+ formData.append('file', file);
104
+
105
+ const res = await fetch('/api/analyze-food', {
106
+ method: 'POST',
107
+ body: formData,
108
+ });
109
+
110
+ const data = await res.json();
111
+ console.log(data.primary_prediction.name); // "Pizza"
112
+ };
113
+ ```
114
 
115
  ## πŸ“Š Supported Categories
116
 
117
  The model recognizes **101 food categories** including:
118
 
119
+ - **Main Courses:** Pizza, Sushi, Ramen, Steak, Hamburger, Lasagna, Tacos, etc.
120
+ - **Desserts:** Cheesecake, Ice Cream, Tiramisu, Donuts, Chocolate Cake, etc.
121
  - **Salads:** Caesar Salad, Greek Salad, Caprese Salad, etc.
122
+ - **Fast Food:** French Fries, Hot Dogs, Nachos, Chicken Wings, etc.
 
123
 
124
+ [See full list β†’](https://github.com/stratospark/food-101)
125
 
126
  ## πŸ”¬ Technical Details
127
 
128
  ### Model
129
+ - **Architecture:** ViT (Vision Transformer)
130
+ - **Training Dataset:** Food-101 (101,000 images)
131
+ - **Accuracy:** ~85% on test set
132
+ - **Model ID:** `nateraw/food`
133
 
134
  ### Performance
135
  | Device | Inference Time |
 
138
  | CPU (4 cores) | ~2-3s |
139
 
140
  ### Stack
141
+ - **Framework:** FastAPI
142
+ - **ML:** PyTorch + Transformers
143
+ - **Deployment:** Hugging Face Spaces (Docker)
 
144
 
145
  ## πŸ’‘ Tips for Best Results
146
 
147
+ βœ… **Good Images:**
148
  - Well-lit, focused photos
149
  - Food fills most of the frame
150
+ - Clear view of the dish
151
+ - Single item per image
152
 
153
+ ❌ **Avoid:**
154
+ - Dark or blurry images
155
+ - Multiple different foods
156
+ - Extreme angles
157
+ - Very small images (<200px)
158
 
159
+ ## πŸ› οΈ Local Development
160
 
 
161
  ```bash
162
+ # Install dependencies
163
  pip install -r requirements.txt
 
164
 
165
+ # Run server
 
166
  python app.py
 
167
 
168
+ # Server will start on http://localhost:7860
169
+ # API docs at http://localhost:7860/docs
170
+ ```
171
 
172
  ## πŸ“ License
173
 
 
177
 
178
  ## ⚠️ Disclaimer
179
 
180
+ Nutritional information is estimated based on typical values. For precise data, consult product packaging or a registered dietitian.
181
 
182
  ## 🀝 Credits
183
 
184
+ - **Model:** [nateraw/food](https://huggingface.co/nateraw/food)
185
+ - **Dataset:** [Food-101](https://data.vision.ee.ethz.ch/cvl/datasets_extra/food-101/)
186
+ - **Framework:** [FastAPI](https://fastapi.tiangolo.com/) + [Transformers](https://huggingface.co/transformers)
 
187
 
188
  ---
189
 
190
+ **Made with ❀️ using PyTorch and FastAPI**
 
 
app.py CHANGED
@@ -1,323 +1,149 @@
1
  #!/usr/bin/env python3
2
  """
3
- 🍽️ AI Food Scanner - Production-Ready System
4
- ================================================
5
-
6
- Produkciono spreman AI sistem za skeniranje hrane sa Gradio interfejsom.
7
-
8
- Ključne karakteristike:
9
- - βœ… Optimizovan za Hugging Face Spaces (free tier - CPU/T4 GPU)
10
- - βœ… EfficientNet-B0 model - najbolji balans brzine i tačnosti
11
- - βœ… Food-101 dataset klasifikacija (101 kategorija hrane)
12
- - βœ… Moderan Gradio UI sa prikazom rezultata
13
- - βœ… Detaljne nutritivne informacije
14
- - βœ… Analiza kvaliteta slike
15
- - βœ… Sve lokalno - bez vanjskih API ključeva
16
-
17
- Model: EfficientNet-B0 pretrained on Food-101
18
- Tačnost: ~85-90% na Food-101 datasetu
19
- Brzina: <2 sekunde po slici na CPU, <0.5s na GPU
20
-
21
- Autor: AI Assistant
22
- Verzija: 1.0.0
23
  """
24
 
25
  import os
26
  import logging
27
- from typing import Dict, Any, Tuple, Optional
28
- from pathlib import Path
29
 
30
- import gradio as gr
31
- import numpy as np
32
- from PIL import Image, ImageEnhance
33
  import torch
34
  import torch.nn.functional as F
35
- from transformers import AutoFeatureExtractor, AutoModelForImageClassification
 
 
 
 
 
 
36
 
37
- # ==================== SETUP LOGGING ====================
 
 
38
  logging.basicConfig(
39
  level=logging.INFO,
40
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
41
  )
42
  logger = logging.getLogger(__name__)
43
 
44
-
45
  # ==================== FOOD-101 CATEGORIES ====================
46
- # Kompletna lista kategorija iz Food-101 dataseta
47
- FOOD_CATEGORIES = [
48
- "apple_pie", "baby_back_ribs", "baklava", "beef_carpaccio", "beef_tartare",
49
- "beet_salad", "beignets", "bibimbap", "bread_pudding", "breakfast_burrito",
50
- "bruschetta", "caesar_salad", "cannoli", "caprese_salad", "carrot_cake",
51
- "ceviche", "cheese_plate", "cheesecake", "chicken_curry", "chicken_quesadilla",
52
- "chicken_wings", "chocolate_cake", "chocolate_mousse", "churros", "clam_chowder",
53
- "club_sandwich", "crab_cakes", "creme_brulee", "croque_madame", "cup_cakes",
54
- "deviled_eggs", "donuts", "dumplings", "edamame", "eggs_benedict",
55
- "escargots", "falafel", "filet_mignon", "fish_and_chips", "foie_gras",
56
- "french_fries", "french_onion_soup", "french_toast", "fried_calamari", "fried_rice",
57
- "frozen_yogurt", "garlic_bread", "gnocchi", "greek_salad", "grilled_cheese_sandwich",
58
- "grilled_salmon", "guacamole", "gyoza", "hamburger", "hot_and_sour_soup",
59
- "hot_dog", "huevos_rancheros", "hummus", "ice_cream", "lasagna",
60
- "lobster_bisque", "lobster_roll_sandwich", "macaroni_and_cheese", "macarons", "miso_soup",
61
- "mussels", "nachos", "omelette", "onion_rings", "oysters",
62
- "pad_thai", "paella", "pancakes", "panna_cotta", "peking_duck",
63
- "pho", "pizza", "pork_chop", "poutine", "prime_rib",
64
- "pulled_pork_sandwich", "ramen", "ravioli", "red_velvet_cake", "risotto",
65
- "samosa", "sashimi", "scallops", "seaweed_salad", "shrimp_and_grits",
66
- "spaghetti_bolognese", "spaghetti_carbonara", "spring_rolls", "steak", "strawberry_shortcake",
67
- "sushi", "tacos", "takoyaki", "tiramisu", "tuna_tartare",
68
- "waffles"
69
- ]
70
-
71
- # Mapiranje kategorija na čitljive nazive
72
- FOOD_NAMES = {
73
- "apple_pie": "Apple Pie",
74
- "baby_back_ribs": "Baby Back Ribs",
75
- "baklava": "Baklava",
76
- "beef_carpaccio": "Beef Carpaccio",
77
- "beef_tartare": "Beef Tartare",
78
- "beet_salad": "Beet Salad",
79
- "beignets": "Beignets",
80
- "bibimbap": "Bibimbap",
81
- "bread_pudding": "Bread Pudding",
82
- "breakfast_burrito": "Breakfast Burrito",
83
- "bruschetta": "Bruschetta",
84
- "caesar_salad": "Caesar Salad",
85
- "cannoli": "Cannoli",
86
- "caprese_salad": "Caprese Salad",
87
- "carrot_cake": "Carrot Cake",
88
- "ceviche": "Ceviche",
89
- "cheese_plate": "Cheese Plate",
90
- "cheesecake": "Cheesecake",
91
- "chicken_curry": "Chicken Curry",
92
- "chicken_quesadilla": "Chicken Quesadilla",
93
- "chicken_wings": "Chicken Wings",
94
- "chocolate_cake": "Chocolate Cake",
95
- "chocolate_mousse": "Chocolate Mousse",
96
- "churros": "Churros",
97
- "clam_chowder": "Clam Chowder",
98
- "club_sandwich": "Club Sandwich",
99
- "crab_cakes": "Crab Cakes",
100
- "creme_brulee": "Creme Brulee",
101
- "croque_madame": "Croque Madame",
102
- "cup_cakes": "Cupcakes",
103
- "deviled_eggs": "Deviled Eggs",
104
- "donuts": "Donuts",
105
- "dumplings": "Dumplings",
106
- "edamame": "Edamame",
107
- "eggs_benedict": "Eggs Benedict",
108
- "escargots": "Escargots",
109
- "falafel": "Falafel",
110
- "filet_mignon": "Filet Mignon",
111
- "fish_and_chips": "Fish and Chips",
112
- "foie_gras": "Foie Gras",
113
- "french_fries": "French Fries",
114
- "french_onion_soup": "French Onion Soup",
115
- "french_toast": "French Toast",
116
- "fried_calamari": "Fried Calamari",
117
- "fried_rice": "Fried Rice",
118
- "frozen_yogurt": "Frozen Yogurt",
119
- "garlic_bread": "Garlic Bread",
120
- "gnocchi": "Gnocchi",
121
- "greek_salad": "Greek Salad",
122
- "grilled_cheese_sandwich": "Grilled Cheese Sandwich",
123
- "grilled_salmon": "Grilled Salmon",
124
- "guacamole": "Guacamole",
125
- "gyoza": "Gyoza",
126
- "hamburger": "Hamburger",
127
- "hot_and_sour_soup": "Hot and Sour Soup",
128
- "hot_dog": "Hot Dog",
129
- "huevos_rancheros": "Huevos Rancheros",
130
- "hummus": "Hummus",
131
- "ice_cream": "Ice Cream",
132
- "lasagna": "Lasagna",
133
- "lobster_bisque": "Lobster Bisque",
134
- "lobster_roll_sandwich": "Lobster Roll Sandwich",
135
- "macaroni_and_cheese": "Macaroni and Cheese",
136
- "macarons": "Macarons",
137
- "miso_soup": "Miso Soup",
138
- "mussels": "Mussels",
139
- "nachos": "Nachos",
140
- "omelette": "Omelette",
141
- "onion_rings": "Onion Rings",
142
- "oysters": "Oysters",
143
- "pad_thai": "Pad Thai",
144
- "paella": "Paella",
145
- "pancakes": "Pancakes",
146
- "panna_cotta": "Panna Cotta",
147
- "peking_duck": "Peking Duck",
148
- "pho": "Pho",
149
- "pizza": "Pizza",
150
- "pork_chop": "Pork Chop",
151
- "poutine": "Poutine",
152
- "prime_rib": "Prime Rib",
153
- "pulled_pork_sandwich": "Pulled Pork Sandwich",
154
- "ramen": "Ramen",
155
- "ravioli": "Ravioli",
156
- "red_velvet_cake": "Red Velvet Cake",
157
- "risotto": "Risotto",
158
- "samosa": "Samosa",
159
- "sashimi": "Sashimi",
160
- "scallops": "Scallops",
161
- "seaweed_salad": "Seaweed Salad",
162
- "shrimp_and_grits": "Shrimp and Grits",
163
- "spaghetti_bolognese": "Spaghetti Bolognese",
164
- "spaghetti_carbonara": "Spaghetti Carbonara",
165
- "spring_rolls": "Spring Rolls",
166
- "steak": "Steak",
167
- "strawberry_shortcake": "Strawberry Shortcake",
168
- "sushi": "Sushi",
169
- "tacos": "Tacos",
170
- "takoyaki": "Takoyaki",
171
- "tiramisu": "Tiramisu",
172
- "tuna_tartare": "Tuna Tartare",
173
- "waffles": "Waffles"
174
  }
175
 
176
-
177
- # ==================== NUTRITIONAL DATABASE ====================
178
- # Jednostavna baza nutritivnih informacija po kategorijama hrane
179
- NUTRITION_DATABASE = {
180
- # Deserti
181
- "apple_pie": {"calories": 237, "protein": 2, "carbs": 34, "fat": 11, "category": "Dessert"},
182
- "baklava": {"calories": 334, "protein": 4, "carbs": 29, "fat": 23, "category": "Dessert"},
183
- "cannoli": {"calories": 213, "protein": 5, "carbs": 25, "fat": 11, "category": "Dessert"},
184
- "carrot_cake": {"calories": 415, "protein": 4, "carbs": 51, "fat": 21, "category": "Dessert"},
185
- "cheesecake": {"calories": 321, "protein": 5, "carbs": 26, "fat": 23, "category": "Dessert"},
186
- "chocolate_cake": {"calories": 352, "protein": 4, "carbs": 51, "fat": 16, "category": "Dessert"},
187
- "chocolate_mousse": {"calories": 214, "protein": 4, "carbs": 23, "fat": 13, "category": "Dessert"},
188
- "churros": {"calories": 237, "protein": 3, "carbs": 29, "fat": 12, "category": "Dessert"},
189
- "creme_brulee": {"calories": 297, "protein": 4, "carbs": 26, "fat": 20, "category": "Dessert"},
190
- "cup_cakes": {"calories": 305, "protein": 4, "carbs": 45, "fat": 13, "category": "Dessert"},
191
- "donuts": {"calories": 269, "protein": 3, "carbs": 31, "fat": 15, "category": "Dessert"},
192
- "ice_cream": {"calories": 207, "protein": 4, "carbs": 24, "fat": 11, "category": "Dessert"},
193
- "macarons": {"calories": 97, "protein": 2, "carbs": 16, "fat": 3, "category": "Dessert"},
194
- "panna_cotta": {"calories": 305, "protein": 3, "carbs": 22, "fat": 23, "category": "Dessert"},
195
- "red_velvet_cake": {"calories": 380, "protein": 4, "carbs": 53, "fat": 18, "category": "Dessert"},
196
- "strawberry_shortcake": {"calories": 247, "protein": 3, "carbs": 38, "fat": 10, "category": "Dessert"},
197
- "tiramisu": {"calories": 240, "protein": 5, "carbs": 26, "fat": 13, "category": "Dessert"},
198
- "waffles": {"calories": 291, "protein": 6, "carbs": 33, "fat": 15, "category": "Dessert"},
199
-
200
- # Glavni obroci
201
- "baby_back_ribs": {"calories": 361, "protein": 27, "carbs": 0, "fat": 27, "category": "Main Course"},
202
- "beef_carpaccio": {"calories": 129, "protein": 22, "carbs": 1, "fat": 4, "category": "Main Course"},
203
- "beef_tartare": {"calories": 220, "protein": 20, "carbs": 2, "fat": 15, "category": "Main Course"},
204
- "bibimbap": {"calories": 560, "protein": 25, "carbs": 80, "fat": 15, "category": "Main Course"},
205
- "chicken_curry": {"calories": 288, "protein": 20, "carbs": 15, "fat": 17, "category": "Main Course"},
206
- "chicken_quesadilla": {"calories": 529, "protein": 27, "carbs": 39, "fat": 29, "category": "Main Course"},
207
- "chicken_wings": {"calories": 203, "protein": 23, "carbs": 0, "fat": 12, "category": "Main Course"},
208
- "filet_mignon": {"calories": 227, "protein": 26, "carbs": 0, "fat": 13, "category": "Main Course"},
209
- "fish_and_chips": {"calories": 585, "protein": 32, "carbs": 51, "fat": 28, "category": "Main Course"},
210
- "grilled_salmon": {"calories": 206, "protein": 22, "carbs": 0, "fat": 12, "category": "Main Course"},
211
- "hamburger": {"calories": 354, "protein": 20, "carbs": 30, "fat": 17, "category": "Main Course"},
212
- "lasagna": {"calories": 315, "protein": 14, "carbs": 30, "fat": 15, "category": "Main Course"},
213
- "pad_thai": {"calories": 429, "protein": 17, "carbs": 61, "fat": 13, "category": "Main Course"},
214
- "paella": {"calories": 525, "protein": 28, "carbs": 58, "fat": 19, "category": "Main Course"},
215
- "peking_duck": {"calories": 337, "protein": 19, "carbs": 1, "fat": 28, "category": "Main Course"},
216
- "pho": {"calories": 350, "protein": 15, "carbs": 45, "fat": 12, "category": "Main Course"},
217
- "pizza": {"calories": 266, "protein": 11, "carbs": 33, "fat": 10, "category": "Main Course"},
218
- "pork_chop": {"calories": 231, "protein": 27, "carbs": 0, "fat": 13, "category": "Main Course"},
219
- "prime_rib": {"calories": 338, "protein": 26, "carbs": 0, "fat": 26, "category": "Main Course"},
220
- "ramen": {"calories": 436, "protein": 15, "carbs": 52, "fat": 19, "category": "Main Course"},
221
- "risotto": {"calories": 200, "protein": 4, "carbs": 30, "fat": 6, "category": "Main Course"},
222
- "spaghetti_bolognese": {"calories": 281, "protein": 14, "carbs": 34, "fat": 10, "category": "Main Course"},
223
- "spaghetti_carbonara": {"calories": 311, "protein": 13, "carbs": 36, "fat": 13, "category": "Main Course"},
224
- "steak": {"calories": 271, "protein": 26, "carbs": 0, "fat": 18, "category": "Main Course"},
225
- "sushi": {"calories": 143, "protein": 6, "carbs": 21, "fat": 4, "category": "Main Course"},
226
- "tacos": {"calories": 226, "protein": 9, "carbs": 20, "fat": 13, "category": "Main Course"},
227
-
228
- # Salate i predjela
229
- "beet_salad": {"calories": 152, "protein": 4, "carbs": 18, "fat": 8, "category": "Salad"},
230
- "caesar_salad": {"calories": 184, "protein": 9, "carbs": 8, "fat": 13, "category": "Salad"},
231
- "caprese_salad": {"calories": 286, "protein": 11, "carbs": 6, "fat": 24, "category": "Salad"},
232
- "greek_salad": {"calories": 107, "protein": 4, "carbs": 8, "fat": 7, "category": "Salad"},
233
- "seaweed_salad": {"calories": 70, "protein": 2, "carbs": 14, "fat": 1, "category": "Salad"},
234
- "bruschetta": {"calories": 77, "protein": 2, "carbs": 11, "fat": 3, "category": "Appetizer"},
235
- "ceviche": {"calories": 130, "protein": 20, "carbs": 8, "fat": 2, "category": "Appetizer"},
236
- "deviled_eggs": {"calories": 145, "protein": 6, "carbs": 1, "fat": 13, "category": "Appetizer"},
237
- "edamame": {"calories": 122, "protein": 11, "carbs": 10, "fat": 5, "category": "Appetizer"},
238
- "falafel": {"calories": 333, "protein": 13, "carbs": 32, "fat": 18, "category": "Appetizer"},
239
- "fried_calamari": {"calories": 175, "protein": 18, "carbs": 8, "fat": 7, "category": "Appetizer"},
240
- "guacamole": {"calories": 150, "protein": 2, "carbs": 9, "fat": 13, "category": "Appetizer"},
241
- "hummus": {"calories": 166, "protein": 5, "carbs": 14, "fat": 10, "category": "Appetizer"},
242
- "spring_rolls": {"calories": 109, "protein": 3, "carbs": 15, "fat": 4, "category": "Appetizer"},
243
-
244
- # Sendviči i brza hrana
245
- "breakfast_burrito": {"calories": 653, "protein": 28, "carbs": 60, "fat": 33, "category": "Fast Food"},
246
- "club_sandwich": {"calories": 590, "protein": 31, "carbs": 47, "fat": 30, "category": "Fast Food"},
247
- "french_fries": {"calories": 312, "protein": 3, "carbs": 37, "fat": 17, "category": "Fast Food"},
248
- "grilled_cheese_sandwich": {"calories": 393, "protein": 17, "carbs": 32, "fat": 22, "category": "Fast Food"},
249
- "hot_dog": {"calories": 290, "protein": 10, "carbs": 24, "fat": 17, "category": "Fast Food"},
250
- "lobster_roll_sandwich": {"calories": 436, "protein": 30, "carbs": 35, "fat": 18, "category": "Fast Food"},
251
- "nachos": {"calories": 346, "protein": 9, "carbs": 36, "fat": 19, "category": "Fast Food"},
252
- "pulled_pork_sandwich": {"calories": 508, "protein": 29, "carbs": 41, "fat": 23, "category": "Fast Food"},
253
-
254
- # Default za ostale kategorije
255
- "default": {"calories": 200, "protein": 10, "carbs": 25, "fat": 8, "category": "Unknown"}
256
  }
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
  # ==================== DEVICE SELECTION ====================
260
  def select_device() -> str:
261
- """
262
- Automatski odabir najboljeg dostupnog device-a.
263
- Optimizovano za Hugging Face Spaces (CPU ili T4 GPU).
264
-
265
- Returns:
266
- str: 'cuda', 'mps', ili 'cpu'
267
- """
268
- # Check for CUDA GPU (HF Spaces T4 GPU)
269
  if torch.cuda.is_available():
270
- device = "cuda"
271
- gpu_name = torch.cuda.get_device_name(0)
272
- gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
273
- logger.info(f"βœ… CUDA available - Using GPU: {gpu_name} ({gpu_memory:.1f} GB)")
274
- # Check for Apple Silicon MPS (local development)
275
  elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
276
- device = "mps"
277
- logger.info("βœ… MPS available - Using Apple Silicon GPU")
278
- # Fallback to CPU (HF Spaces free tier default)
279
  else:
280
- device = "cpu"
281
- logger.info("⚠️ Using CPU - inference will be slower (~2-3s per image)")
282
-
283
- return device
284
-
285
 
286
  # ==================== IMAGE PREPROCESSING ====================
287
  def preprocess_image(image: Image.Image) -> Image.Image:
288
- """
289
- Napredna predobrada slike za bolju klasifikaciju.
290
-
291
- Koraci:
292
- 1. Konverzija u RGB ako nije
293
- 2. PoboljΕ‘anje oΕ‘trine
294
- 3. PoboljΕ‘anje kontrasta
295
- 4. Optimizacija veličine (za memoriju)
296
-
297
- Args:
298
- image: PIL Image objekat
299
-
300
- Returns:
301
- PIL Image: PredobraΔ‘ena slika
302
- """
303
- # Konverzija u RGB
304
  if image.mode != "RGB":
305
  image = image.convert("RGB")
306
 
307
- # PoboljΕ‘anje oΕ‘trine
308
  enhancer = ImageEnhance.Sharpness(image)
309
- image = enhancer.enhance(1.3)
310
-
311
- # PoboljΕ‘anje kontrasta
312
- enhancer = ImageEnhance.Contrast(image)
313
  image = enhancer.enhance(1.2)
314
 
315
- # Blago poboljΕ‘anje boja
316
- enhancer = ImageEnhance.Color(image)
317
- image = enhancer.enhance(1.1)
318
 
319
- # Resize ako je prevelika (za optimizaciju memorije)
320
- max_size = 800
321
  if max(image.size) > max_size:
322
  ratio = max_size / max(image.size)
323
  new_size = tuple(int(dim * ratio) for dim in image.size)
@@ -325,455 +151,191 @@ def preprocess_image(image: Image.Image) -> Image.Image:
325
 
326
  return image
327
 
328
-
329
- # ==================== IMAGE QUALITY ANALYSIS ====================
330
- def analyze_image_quality(image: Image.Image) -> Dict[str, Any]:
331
- """
332
- Analizira kvalitet slike za detekciju problema.
333
-
334
- Args:
335
- image: PIL Image objekat
336
-
337
- Returns:
338
- Dict sa metrikama kvaliteta
339
- """
340
- img_array = np.array(image)
341
-
342
- # Brightness analiza
343
- brightness = float(np.mean(img_array))
344
-
345
- # Color saturation analiza
346
- r, g, b = img_array[:, :, 0], img_array[:, :, 1], img_array[:, :, 2]
347
- saturation = float(np.mean(np.abs(r - g) + np.abs(g - b) + np.abs(b - r)))
348
-
349
- # Texture complexity (variance)
350
- texture_complexity = float(np.var(img_array) / 10000)
351
-
352
- # Overall quality score (0-10)
353
- quality_score = 5.0
354
-
355
- # Brightness assessment
356
- if 80 <= brightness <= 200:
357
- quality_score += 2
358
- elif brightness < 50 or brightness > 220:
359
- quality_score -= 2
360
-
361
- # Saturation assessment
362
- if saturation > 30:
363
- quality_score += 2
364
- elif saturation < 10:
365
- quality_score -= 1
366
-
367
- # Texture assessment
368
- if texture_complexity > 0.1:
369
- quality_score += 1
370
-
371
- quality_score = max(0, min(10, quality_score))
372
-
373
- return {
374
- "brightness": brightness,
375
- "saturation": saturation,
376
- "texture_complexity": texture_complexity,
377
- "quality_score": quality_score,
378
- "width": image.width,
379
- "height": image.height
380
- }
381
-
382
-
383
- # ==================== FOOD RECOGNIZER CLASS ====================
384
  class FoodRecognizer:
385
- """
386
- Glavni AI model za prepoznavanje hrane.
387
-
388
- Koristi EfficientNet-B0 pretrained na Food-101 datasetu.
389
- - 101 kategorija hrane
390
- - ~85-90% tačnost
391
- - Optimizovan za CPU i GPU
392
- """
393
 
394
  def __init__(self, device: str):
395
- """
396
- Inicijalizacija modela.
397
-
398
- Args:
399
- device: 'cuda', 'mps', ili 'cpu'
400
- """
401
  self.device = device
402
  self.model = None
403
- self.feature_extractor = None
404
-
405
- logger.info("πŸ”„ Loading AI model...")
406
  self._load_model()
407
 
408
  def _load_model(self):
409
- """
410
- Učitava EfficientNet-B0 model treniran na Food-101.
411
-
412
- Model se automatski preuzima sa Hugging Face Hub.
413
- """
414
- # Setup cache directory za HF Spaces
415
- cache_dir = os.environ.get("TRANSFORMERS_CACHE", None)
416
-
417
  try:
418
- # Model ID - pretrained na Food-101
419
- model_name = "Kaludi/food-category-classification-v2.0"
420
 
421
- logger.info(f"πŸ“₯ Downloading model: {model_name}")
422
 
423
- # Load kwargs sa cache directory
424
- load_kwargs = {}
425
- if cache_dir:
426
- load_kwargs["cache_dir"] = cache_dir
427
 
428
- # Učitaj feature extractor
429
- self.feature_extractor = AutoFeatureExtractor.from_pretrained(
430
- model_name,
431
- **load_kwargs
432
- )
433
-
434
- # Učitaj model (force safetensors for security)
435
  self.model = AutoModelForImageClassification.from_pretrained(
436
  model_name,
437
- torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
438
- use_safetensors=True, # Force safetensors format (security)
439
  **load_kwargs
440
  )
441
 
442
- # Prebaci na device i postavi u eval mode
443
  self.model = self.model.to(self.device)
444
  self.model.eval()
445
 
446
- logger.info(f"βœ… Model loaded successfully on {self.device.upper()}")
447
 
448
  except Exception as e:
449
  logger.error(f"❌ Failed to load model: {e}")
450
- # Fallback na drugi model ako prvi ne radi
451
- try:
452
- logger.info("πŸ”„ Trying fallback model...")
453
- model_name = "nateraw/food"
454
-
455
- load_kwargs = {}
456
- if cache_dir:
457
- load_kwargs["cache_dir"] = cache_dir
458
-
459
- self.feature_extractor = AutoFeatureExtractor.from_pretrained(
460
- model_name,
461
- **load_kwargs
462
- )
463
- self.model = AutoModelForImageClassification.from_pretrained(
464
- model_name,
465
- use_safetensors=True, # Force safetensors format (security)
466
- **load_kwargs
467
- )
468
- self.model = self.model.to(self.device)
469
- self.model.eval()
470
-
471
- logger.info(f"βœ… Fallback model loaded on {self.device.upper()}")
472
- except Exception as e2:
473
- logger.error(f"❌ Fallback model also failed: {e2}")
474
- raise RuntimeError("Unable to load any model")
475
 
476
  def predict(self, image: Image.Image, top_k: int = 5) -> Dict[str, Any]:
477
- """
478
- Predikcija kategorije hrane iz slike.
479
-
480
- Args:
481
- image: PIL Image objekat
482
- top_k: Broj top rezultata za vratiti
483
-
484
- Returns:
485
- Dict sa rezultatima predikcije
486
- """
487
- # Predobrada slike
488
  processed_image = preprocess_image(image)
489
 
490
- # Analiza kvaliteta
491
- quality_metrics = analyze_image_quality(processed_image)
492
-
493
- # Ekstrakcija features-a
494
- inputs = self.feature_extractor(
495
- images=processed_image,
496
- return_tensors="pt"
497
- )
498
-
499
- # Prebaci na device
500
  inputs = {k: v.to(self.device) for k, v in inputs.items()}
501
 
502
- # Predikcija (bez gradijenta)
503
  with torch.no_grad():
504
  outputs = self.model(**inputs)
505
  logits = outputs.logits
 
506
 
507
- # Softmax za probabilnosti
508
- probs = F.softmax(logits, dim=-1)
509
- probs = probs.cpu().numpy()[0]
510
-
511
- # Top K rezultata
512
  top_indices = np.argsort(probs)[::-1][:top_k]
513
 
514
  results = []
515
  for idx in top_indices:
516
- label = self.model.config.id2label[idx]
517
  confidence = float(probs[idx])
518
 
519
- # Dobij čitljivo ime
520
- readable_name = FOOD_NAMES.get(label, label.replace("_", " ").title())
521
 
522
  results.append({
523
- "label": label,
524
  "name": readable_name,
525
  "confidence": confidence
526
  })
527
 
528
- # Glavni rezultat
529
- primary_result = results[0]
530
-
531
- # Dobij nutritivne informacije
532
- nutrition = self._get_nutrition(primary_result["label"])
533
 
534
  return {
535
- "primary_prediction": primary_result,
 
536
  "top_predictions": results,
537
  "nutrition": nutrition,
538
- "image_quality": quality_metrics,
539
  "model_info": {
540
- "device": self.device.upper(),
541
- "model_type": "EfficientNet-B0",
542
  "dataset": "Food-101",
543
- "num_categories": 101
 
544
  }
545
  }
546
 
547
- def _get_nutrition(self, food_label: str) -> Dict[str, Any]:
548
- """
549
- Dobija nutritivne informacije za hranu.
 
550
 
551
- Args:
552
- food_label: Oznaka kategorije hrane
 
553
 
554
- Returns:
555
- Dict sa nutritivnim informacijama
556
- """
557
- # Ako postoji u bazi, vrati tačne podatke
558
- if food_label in NUTRITION_DATABASE:
559
- nutrition = NUTRITION_DATABASE[food_label].copy()
560
- else:
561
- # Inače vrati default estimate
562
- nutrition = NUTRITION_DATABASE["default"].copy()
 
 
 
 
 
 
563
 
564
- # Dodaj readable name
565
- nutrition["food_name"] = FOOD_NAMES.get(food_label, food_label.replace("_", " ").title())
566
 
567
- return nutrition
 
 
 
 
 
 
 
 
 
 
568
 
 
 
 
 
 
 
 
 
569
 
570
- # ==================== GRADIO INTERFACE ====================
571
- def create_gradio_interface(recognizer: FoodRecognizer) -> gr.Blocks:
572
  """
573
- Kreira moderan Gradio interfejs za AI Food Scanner.
574
 
575
  Args:
576
- recognizer: FoodRecognizer instanca
577
 
578
  Returns:
579
- Gradio Blocks interfejs
580
  """
581
-
582
- def predict_food(image):
583
- """
584
- Wrapper funkcija za Gradio - procesira sliku i vraća rezultate.
585
- """
586
- if image is None:
587
- return None, "⚠️ Please upload an image first!"
588
-
589
- try:
590
- # Konvertuj u PIL Image ako već nije
591
- if not isinstance(image, Image.Image):
592
- image = Image.fromarray(image)
593
-
594
- # Predikcija
595
- logger.info("πŸ” Processing image...")
596
- results = recognizer.predict(image, top_k=5)
597
-
598
- # Formatiraj output tekst
599
- primary = results["primary_prediction"]
600
- nutrition = results["nutrition"]
601
- quality = results["image_quality"]
602
-
603
- output_text = f"""
604
- # 🍽️ Detection Results
605
-
606
- ## Primary Match
607
- **{primary['name']}**
608
- Confidence: **{primary['confidence']:.1%}**
609
-
610
- ## Top 5 Predictions
611
- """
612
- for i, pred in enumerate(results["top_predictions"], 1):
613
- bar_length = int(pred['confidence'] * 20)
614
- bar = "β–ˆ" * bar_length + "β–‘" * (20 - bar_length)
615
- output_text += f"{i}. **{pred['name']}** - {pred['confidence']:.1%}\n `{bar}`\n\n"
616
-
617
- output_text += f"""
618
- ---
619
-
620
- ## πŸ“Š Nutritional Information
621
- (per 100g serving)
622
-
623
- - **Calories:** {nutrition['calories']} kcal
624
- - **Protein:** {nutrition['protein']}g
625
- - **Carbohydrates:** {nutrition['carbs']}g
626
- - **Fat:** {nutrition['fat']}g
627
- - **Category:** {nutrition['category']}
628
-
629
- ---
630
-
631
- ## πŸ–ΌοΈ Image Quality Analysis
632
-
633
- - **Quality Score:** {quality['quality_score']:.1f}/10
634
- - **Brightness:** {quality['brightness']:.0f}
635
- - **Saturation:** {quality['saturation']:.1f}
636
- - **Resolution:** {quality['width']}x{quality['height']}px
637
-
638
- ---
639
-
640
- ## πŸ€– Model Information
641
-
642
- - **Model:** {results['model_info']['model_type']}
643
- - **Dataset:** {results['model_info']['dataset']}
644
- - **Categories:** {results['model_info']['num_categories']}
645
- - **Device:** {results['model_info']['device']}
646
- """
647
-
648
- return image, output_text
649
-
650
- except Exception as e:
651
- logger.error(f"❌ Prediction error: {e}")
652
- return None, f"❌ **Error:** {str(e)}\n\nPlease try another image."
653
-
654
- # Health check funkcija za monitoring (API endpoint)
655
- def health_check():
656
- """Simple health check that returns OK status."""
657
- return {"status": "healthy", "model_loaded": True}
658
-
659
- # Kreiraj Gradio interfejs
660
- with gr.Blocks(
661
- title="AI Food Scanner",
662
- theme=gr.themes.Soft()
663
- ) as demo:
664
-
665
- gr.Markdown("""
666
- # 🍽️ AI Food Scanner
667
-
668
- Upload an image of food to detect its type and get nutritional information.
669
-
670
- **Powered by EfficientNet-B0** trained on Food-101 dataset (101 food categories).
671
- """)
672
-
673
- with gr.Row():
674
- with gr.Column(scale=1):
675
- # Input: slika
676
- input_image = gr.Image(
677
- label="πŸ“Έ Upload Food Image",
678
- type="pil",
679
- sources=["upload", "clipboard"],
680
- )
681
-
682
- # Button za analizu
683
- analyze_btn = gr.Button(
684
- "πŸ” Analyze Food",
685
- variant="primary",
686
- size="lg"
687
- )
688
-
689
- # Primjeri (ako postoje)
690
- gr.Examples(
691
- examples=[], # Dodaj putanje do primjera ako imaΕ‘
692
- inputs=input_image,
693
- label="πŸ“‹ Example Images"
694
- )
695
-
696
- with gr.Column(scale=1):
697
- # Output: procesirana slika
698
- output_image = gr.Image(
699
- label="πŸ–ΌοΈ Processed Image",
700
- type="pil"
701
- )
702
-
703
- # Output: rezultati
704
- output_text = gr.Markdown(
705
- label="πŸ“Š Analysis Results",
706
- value="*Results will appear here...*"
707
- )
708
-
709
- # PoveΕΎi button sa funkcijom
710
- analyze_btn.click(
711
- fn=predict_food,
712
- inputs=input_image,
713
- outputs=[output_image, output_text]
714
  )
715
 
716
- gr.Markdown("""
717
- ---
718
-
719
- ## πŸ“– About This System
720
 
721
- This AI Food Scanner uses **EfficientNet-B0** model pretrained on the **Food-101** dataset.
 
 
722
 
723
- ### Features:
724
- - βœ… 101 food categories recognition
725
- - βœ… ~85-90% accuracy
726
- - βœ… Nutritional information database
727
- - βœ… Image quality analysis
728
- - βœ… Optimized for CPU and GPU
729
- - βœ… Production-ready deployment
730
 
731
- ### Technology Stack:
732
- - **Model:** EfficientNet-B0
733
- - **Framework:** PyTorch + Transformers
734
- - **Interface:** Gradio
735
- - **Deployment:** Hugging Face Spaces compatible
736
 
737
- **Note:** Nutritional values are estimates per 100g serving.
738
- """)
 
739
 
740
- return demo
741
-
742
-
743
- # ==================== MAIN APPLICATION ====================
744
- def main():
745
- """
746
- Glavna funkcija - pokreće aplikaciju.
747
- """
748
- logger.info("=" * 80)
749
- logger.info("🍽️ AI FOOD SCANNER - PRODUCTION READY SYSTEM")
750
- logger.info("=" * 80)
751
-
752
- # Selektuj device
753
- device = select_device()
754
-
755
- # Inicijalizuj model
756
- logger.info("πŸš€ Initializing Food Recognition System...")
757
- recognizer = FoodRecognizer(device)
758
-
759
- # Kreiraj Gradio interfejs
760
- logger.info("🎨 Creating Gradio Interface...")
761
- demo = create_gradio_interface(recognizer)
762
 
763
- # Pokreni server
764
  logger.info("=" * 80)
765
- logger.info("βœ… System ready! Launching Gradio interface...")
 
 
766
  logger.info("=" * 80)
767
 
768
- # Launch sa konfiguracijom za Hugging Face Spaces
769
- demo.launch(
770
- server_name="0.0.0.0",
771
- server_port=int(os.environ.get("PORT", 7860)),
772
- share=False,
773
- show_error=True,
774
- auth=None # No authentication required
775
  )
776
-
777
-
778
- if __name__ == "__main__":
779
- main()
 
1
  #!/usr/bin/env python3
2
  """
3
+ 🍽️ Food Recognition API - Production Ready
4
+ ============================================
5
+
6
+ FastAPI backend za food recognition optimizovan za Hugging Face Spaces.
7
+ - Koristi PRAVI Food-101 pretrained model
8
+ - REST API endpoint: POST /api/analyze-food
9
+ - CORS enabled za Next.js integraciju
10
+ - 101 kategorija hrane sa visokom tačnoΕ‘Δ‡u
11
+
12
+ Model: nateraw/food (Food-101 dataset - 101 classes)
13
+ Accuracy: ~85% na Food-101 test set
 
 
 
 
 
 
 
 
 
14
  """
15
 
16
  import os
17
  import logging
18
+ from typing import Dict, Any, List, Optional
19
+ from io import BytesIO
20
 
 
 
 
21
  import torch
22
  import torch.nn.functional as F
23
+ from PIL import Image, ImageEnhance
24
+ import numpy as np
25
+
26
+ from fastapi import FastAPI, File, UploadFile, HTTPException
27
+ from fastapi.middleware.cors import CORSMiddleware
28
+ from fastapi.responses import JSONResponse
29
+ import uvicorn
30
 
31
+ from transformers import AutoImageProcessor, AutoModelForImageClassification
32
+
33
+ # ==================== LOGGING ====================
34
  logging.basicConfig(
35
  level=logging.INFO,
36
+ format='%(asctime)s - %(levelname)s - %(message)s'
37
  )
38
  logger = logging.getLogger(__name__)
39
 
 
40
  # ==================== FOOD-101 CATEGORIES ====================
41
+ FOOD_CATEGORIES = {
42
+ 0: "apple_pie", 1: "baby_back_ribs", 2: "baklava", 3: "beef_carpaccio", 4: "beef_tartare",
43
+ 5: "beet_salad", 6: "beignets", 7: "bibimbap", 8: "bread_pudding", 9: "breakfast_burrito",
44
+ 10: "bruschetta", 11: "caesar_salad", 12: "cannoli", 13: "caprese_salad", 14: "carrot_cake",
45
+ 15: "ceviche", 16: "cheese_plate", 17: "cheesecake", 18: "chicken_curry", 19: "chicken_quesadilla",
46
+ 20: "chicken_wings", 21: "chocolate_cake", 22: "chocolate_mousse", 23: "churros", 24: "clam_chowder",
47
+ 25: "club_sandwich", 26: "crab_cakes", 27: "creme_brulee", 28: "croque_madame", 29: "cup_cakes",
48
+ 30: "deviled_eggs", 31: "donuts", 32: "dumplings", 33: "edamame", 34: "eggs_benedict",
49
+ 35: "escargots", 36: "falafel", 37: "filet_mignon", 38: "fish_and_chips", 39: "foie_gras",
50
+ 40: "french_fries", 41: "french_onion_soup", 42: "french_toast", 43: "fried_calamari", 44: "fried_rice",
51
+ 45: "frozen_yogurt", 46: "garlic_bread", 47: "gnocchi", 48: "greek_salad", 49: "grilled_cheese_sandwich",
52
+ 50: "grilled_salmon", 51: "guacamole", 52: "gyoza", 53: "hamburger", 54: "hot_and_sour_soup",
53
+ 55: "hot_dog", 56: "huevos_rancheros", 57: "hummus", 58: "ice_cream", 59: "lasagna",
54
+ 60: "lobster_bisque", 61: "lobster_roll_sandwich", 62: "macaroni_and_cheese", 63: "macarons", 64: "miso_soup",
55
+ 65: "mussels", 66: "nachos", 67: "omelette", 68: "onion_rings", 69: "oysters",
56
+ 70: "pad_thai", 71: "paella", 72: "pancakes", 73: "panna_cotta", 74: "peking_duck",
57
+ 75: "pho", 76: "pizza", 77: "pork_chop", 78: "poutine", 79: "prime_rib",
58
+ 80: "pulled_pork_sandwich", 81: "ramen", 82: "ravioli", 83: "red_velvet_cake", 84: "risotto",
59
+ 85: "samosa", 86: "sashimi", 87: "scallops", 88: "seaweed_salad", 89: "shrimp_and_grits",
60
+ 90: "spaghetti_bolognese", 91: "spaghetti_carbonara", 92: "spring_rolls", 93: "steak", 94: "strawberry_shortcake",
61
+ 95: "sushi", 96: "tacos", 97: "takoyaki", 98: "tiramisu", 99: "tuna_tartare", 100: "waffles"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
63
 
64
+ # Readable names
65
+ FOOD_NAMES = {
66
+ "apple_pie": "Apple Pie", "baby_back_ribs": "Baby Back Ribs", "baklava": "Baklava",
67
+ "beef_carpaccio": "Beef Carpaccio", "beef_tartare": "Beef Tartare", "beet_salad": "Beet Salad",
68
+ "beignets": "Beignets", "bibimbap": "Bibimbap", "bread_pudding": "Bread Pudding",
69
+ "breakfast_burrito": "Breakfast Burrito", "bruschetta": "Bruschetta", "caesar_salad": "Caesar Salad",
70
+ "cannoli": "Cannoli", "caprese_salad": "Caprese Salad", "carrot_cake": "Carrot Cake",
71
+ "ceviche": "Ceviche", "cheese_plate": "Cheese Plate", "cheesecake": "Cheesecake",
72
+ "chicken_curry": "Chicken Curry", "chicken_quesadilla": "Chicken Quesadilla",
73
+ "chicken_wings": "Chicken Wings", "chocolate_cake": "Chocolate Cake",
74
+ "chocolate_mousse": "Chocolate Mousse", "churros": "Churros", "clam_chowder": "Clam Chowder",
75
+ "club_sandwich": "Club Sandwich", "crab_cakes": "Crab Cakes", "creme_brulee": "Creme Brulee",
76
+ "croque_madame": "Croque Madame", "cup_cakes": "Cupcakes", "deviled_eggs": "Deviled Eggs",
77
+ "donuts": "Donuts", "dumplings": "Dumplings", "edamame": "Edamame",
78
+ "eggs_benedict": "Eggs Benedict", "escargots": "Escargots", "falafel": "Falafel",
79
+ "filet_mignon": "Filet Mignon", "fish_and_chips": "Fish and Chips", "foie_gras": "Foie Gras",
80
+ "french_fries": "French Fries", "french_onion_soup": "French Onion Soup",
81
+ "french_toast": "French Toast", "fried_calamari": "Fried Calamari", "fried_rice": "Fried Rice",
82
+ "frozen_yogurt": "Frozen Yogurt", "garlic_bread": "Garlic Bread", "gnocchi": "Gnocchi",
83
+ "greek_salad": "Greek Salad", "grilled_cheese_sandwich": "Grilled Cheese Sandwich",
84
+ "grilled_salmon": "Grilled Salmon", "guacamole": "Guacamole", "gyoza": "Gyoza",
85
+ "hamburger": "Hamburger", "hot_and_sour_soup": "Hot and Sour Soup", "hot_dog": "Hot Dog",
86
+ "huevos_rancheros": "Huevos Rancheros", "hummus": "Hummus", "ice_cream": "Ice Cream",
87
+ "lasagna": "Lasagna", "lobster_bisque": "Lobster Bisque",
88
+ "lobster_roll_sandwich": "Lobster Roll Sandwich", "macaroni_and_cheese": "Macaroni and Cheese",
89
+ "macarons": "Macarons", "miso_soup": "Miso Soup", "mussels": "Mussels", "nachos": "Nachos",
90
+ "omelette": "Omelette", "onion_rings": "Onion Rings", "oysters": "Oysters",
91
+ "pad_thai": "Pad Thai", "paella": "Paella", "pancakes": "Pancakes", "panna_cotta": "Panna Cotta",
92
+ "peking_duck": "Peking Duck", "pho": "Pho", "pizza": "Pizza", "pork_chop": "Pork Chop",
93
+ "poutine": "Poutine", "prime_rib": "Prime Rib", "pulled_pork_sandwich": "Pulled Pork Sandwich",
94
+ "ramen": "Ramen", "ravioli": "Ravioli", "red_velvet_cake": "Red Velvet Cake",
95
+ "risotto": "Risotto", "samosa": "Samosa", "sashimi": "Sashimi", "scallops": "Scallops",
96
+ "seaweed_salad": "Seaweed Salad", "shrimp_and_grits": "Shrimp and Grits",
97
+ "spaghetti_bolognese": "Spaghetti Bolognese", "spaghetti_carbonara": "Spaghetti Carbonara",
98
+ "spring_rolls": "Spring Rolls", "steak": "Steak", "strawberry_shortcake": "Strawberry Shortcake",
99
+ "sushi": "Sushi", "tacos": "Tacos", "takoyaki": "Takoyaki", "tiramisu": "Tiramisu",
100
+ "tuna_tartare": "Tuna Tartare", "waffles": "Waffles"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  }
102
 
103
+ # Nutrition database
104
+ NUTRITION_DB = {
105
+ "pizza": {"calories": 266, "protein": 11, "carbs": 33, "fat": 10},
106
+ "hamburger": {"calories": 354, "protein": 20, "carbs": 30, "fat": 17},
107
+ "sushi": {"calories": 143, "protein": 6, "carbs": 21, "fat": 4},
108
+ "ice_cream": {"calories": 207, "protein": 4, "carbs": 24, "fat": 11},
109
+ "french_fries": {"calories": 312, "protein": 3, "carbs": 37, "fat": 17},
110
+ "chicken_wings": {"calories": 203, "protein": 23, "carbs": 0, "fat": 12},
111
+ "chocolate_cake": {"calories": 352, "protein": 4, "carbs": 51, "fat": 16},
112
+ "caesar_salad": {"calories": 184, "protein": 9, "carbs": 8, "fat": 13},
113
+ "steak": {"calories": 271, "protein": 26, "carbs": 0, "fat": 18},
114
+ "tacos": {"calories": 226, "protein": 9, "carbs": 20, "fat": 13},
115
+ # Default for others
116
+ "_default": {"calories": 200, "protein": 10, "carbs": 25, "fat": 8}
117
+ }
118
 
119
  # ==================== DEVICE SELECTION ====================
120
  def select_device() -> str:
121
+ """Select best available device."""
 
 
 
 
 
 
 
122
  if torch.cuda.is_available():
123
+ logger.info(f"βœ… Using CUDA GPU: {torch.cuda.get_device_name(0)}")
124
+ return "cuda"
 
 
 
125
  elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
126
+ logger.info("βœ… Using Apple Silicon GPU (MPS)")
127
+ return "mps"
 
128
  else:
129
+ logger.info("⚠️ Using CPU")
130
+ return "cpu"
 
 
 
131
 
132
  # ==================== IMAGE PREPROCESSING ====================
133
  def preprocess_image(image: Image.Image) -> Image.Image:
134
+ """Enhanced image preprocessing."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  if image.mode != "RGB":
136
  image = image.convert("RGB")
137
 
138
+ # Enhance image
139
  enhancer = ImageEnhance.Sharpness(image)
 
 
 
 
140
  image = enhancer.enhance(1.2)
141
 
142
+ enhancer = ImageEnhance.Contrast(image)
143
+ image = enhancer.enhance(1.15)
 
144
 
145
+ # Resize if too large
146
+ max_size = 512
147
  if max(image.size) > max_size:
148
  ratio = max_size / max(image.size)
149
  new_size = tuple(int(dim * ratio) for dim in image.size)
 
151
 
152
  return image
153
 
154
+ # ==================== FOOD RECOGNIZER ====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  class FoodRecognizer:
156
+ """Food recognition using Food-101 trained model."""
 
 
 
 
 
 
 
157
 
158
  def __init__(self, device: str):
 
 
 
 
 
 
159
  self.device = device
160
  self.model = None
161
+ self.processor = None
 
 
162
  self._load_model()
163
 
164
  def _load_model(self):
165
+ """Load Food-101 trained model."""
 
 
 
 
 
 
 
166
  try:
167
+ # Use nateraw/food - PRAVI Food-101 model
168
+ model_name = "nateraw/food"
169
 
170
+ logger.info(f"πŸ“₯ Loading model: {model_name}")
171
 
172
+ # Setup cache
173
+ cache_dir = os.environ.get("TRANSFORMERS_CACHE", None)
174
+ load_kwargs = {"cache_dir": cache_dir} if cache_dir else {}
 
175
 
176
+ # Load processor and model
177
+ self.processor = AutoImageProcessor.from_pretrained(model_name, **load_kwargs)
 
 
 
 
 
178
  self.model = AutoModelForImageClassification.from_pretrained(
179
  model_name,
180
+ use_safetensors=True,
 
181
  **load_kwargs
182
  )
183
 
 
184
  self.model = self.model.to(self.device)
185
  self.model.eval()
186
 
187
+ logger.info(f"βœ… Model loaded on {self.device.upper()}")
188
 
189
  except Exception as e:
190
  logger.error(f"❌ Failed to load model: {e}")
191
+ raise RuntimeError(f"Model loading failed: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
  def predict(self, image: Image.Image, top_k: int = 5) -> Dict[str, Any]:
194
+ """Predict food category from image."""
195
+ # Preprocess
 
 
 
 
 
 
 
 
 
196
  processed_image = preprocess_image(image)
197
 
198
+ # Prepare inputs
199
+ inputs = self.processor(images=processed_image, return_tensors="pt")
 
 
 
 
 
 
 
 
200
  inputs = {k: v.to(self.device) for k, v in inputs.items()}
201
 
202
+ # Inference
203
  with torch.no_grad():
204
  outputs = self.model(**inputs)
205
  logits = outputs.logits
206
+ probs = F.softmax(logits, dim=-1).cpu().numpy()[0]
207
 
208
+ # Get top K predictions
 
 
 
 
209
  top_indices = np.argsort(probs)[::-1][:top_k]
210
 
211
  results = []
212
  for idx in top_indices:
213
+ label_key = self.model.config.id2label[idx]
214
  confidence = float(probs[idx])
215
 
216
+ # Get readable name
217
+ readable_name = FOOD_NAMES.get(label_key, label_key.replace("_", " ").title())
218
 
219
  results.append({
220
+ "label": label_key,
221
  "name": readable_name,
222
  "confidence": confidence
223
  })
224
 
225
+ # Get nutrition info
226
+ primary_label = results[0]["label"]
227
+ nutrition = NUTRITION_DB.get(primary_label, NUTRITION_DB["_default"]).copy()
228
+ nutrition["food_name"] = results[0]["name"]
 
229
 
230
  return {
231
+ "success": True,
232
+ "primary_prediction": results[0],
233
  "top_predictions": results,
234
  "nutrition": nutrition,
 
235
  "model_info": {
236
+ "model": "nateraw/food",
 
237
  "dataset": "Food-101",
238
+ "num_classes": 101,
239
+ "device": self.device.upper()
240
  }
241
  }
242
 
243
+ # ==================== FASTAPI APP ====================
244
+ logger.info("=" * 80)
245
+ logger.info("🍽️ FOOD RECOGNITION API - STARTING")
246
+ logger.info("=" * 80)
247
 
248
+ # Initialize model
249
+ device = select_device()
250
+ recognizer = FoodRecognizer(device)
251
 
252
+ # Create FastAPI app
253
+ app = FastAPI(
254
+ title="Food Recognition API",
255
+ description="AI-powered food recognition with 101 categories",
256
+ version="1.0.0"
257
+ )
258
+
259
+ # CORS - enable all origins for Next.js
260
+ app.add_middleware(
261
+ CORSMiddleware,
262
+ allow_origins=["*"], # Allow all origins (adjust in production)
263
+ allow_credentials=True,
264
+ allow_methods=["*"],
265
+ allow_headers=["*"],
266
+ )
267
 
268
+ # ==================== API ENDPOINTS ====================
 
269
 
270
+ @app.get("/")
271
+ def root():
272
+ """Root endpoint."""
273
+ return {
274
+ "message": "Food Recognition API",
275
+ "status": "online",
276
+ "endpoints": {
277
+ "POST /api/analyze-food": "Analyze food image",
278
+ "GET /health": "Health check"
279
+ }
280
+ }
281
 
282
+ @app.get("/health")
283
+ def health():
284
+ """Health check endpoint."""
285
+ return {
286
+ "status": "healthy",
287
+ "model_loaded": recognizer.model is not None,
288
+ "device": device.upper()
289
+ }
290
 
291
+ @app.post("/api/analyze-food")
292
+ async def analyze_food(file: UploadFile = File(...)):
293
  """
294
+ Analyze food image.
295
 
296
  Args:
297
+ file: Image file (JPEG, PNG, WebP)
298
 
299
  Returns:
300
+ JSON with food recognition results
301
  """
302
+ # Validate file type
303
+ if file.content_type not in ["image/jpeg", "image/jpg", "image/png", "image/webp"]:
304
+ raise HTTPException(
305
+ status_code=400,
306
+ detail="Invalid file type. Supported: JPEG, PNG, WebP"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  )
308
 
309
+ try:
310
+ # Read image
311
+ contents = await file.read()
312
+ image = Image.open(BytesIO(contents))
313
 
314
+ # Predict
315
+ logger.info(f"πŸ” Analyzing image: {file.filename}")
316
+ results = recognizer.predict(image, top_k=5)
317
 
318
+ logger.info(f"βœ… Prediction: {results['primary_prediction']['name']} ({results['primary_prediction']['confidence']:.2%})")
 
 
 
 
 
 
319
 
320
+ return JSONResponse(content=results)
 
 
 
 
321
 
322
+ except Exception as e:
323
+ logger.error(f"❌ Error: {e}")
324
+ raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
325
 
326
+ # ==================== MAIN ====================
327
+ if __name__ == "__main__":
328
+ port = int(os.environ.get("PORT", 7860))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
 
330
  logger.info("=" * 80)
331
+ logger.info("βœ… API Ready!")
332
+ logger.info(f"πŸ“‘ Server: http://0.0.0.0:{port}")
333
+ logger.info(f"πŸ“– Docs: http://0.0.0.0:{port}/docs")
334
  logger.info("=" * 80)
335
 
336
+ uvicorn.run(
337
+ app,
338
+ host="0.0.0.0",
339
+ port=port,
340
+ log_level="info"
 
 
341
  )
 
 
 
 
requirements.txt CHANGED
@@ -1,41 +1,26 @@
1
- # AI Food Scanner - Production Requirements
2
- # Optimized for Hugging Face Spaces (Free Tier: CPU/T4 GPU, 16-24GB RAM)
3
 
4
- # ==================== Core Dependencies ====================
5
- # Python 3.9+ required
 
 
6
 
7
- # Deep Learning Framework (upgraded for security CVE-2025-32434)
8
  torch>=2.6.0
9
  torchvision>=0.20.0
10
-
11
- # Hugging Face Transformers (for pretrained models)
12
  transformers>=4.35.0
13
 
14
- # ==================== UI Framework ====================
15
- # Gradio - Modern UI for ML demos
16
- gradio>=4.0.0
17
-
18
  # ==================== Image Processing ====================
19
- # PIL/Pillow - Image manipulation
20
  Pillow>=10.0.0
 
21
 
22
- # NumPy - Numerical operations
23
- numpy>=1.21.0,<2.0.0
24
-
25
- # ==================== Optional Optimizations ====================
26
- # Accelerate - Faster model loading and inference
27
  accelerate>=0.20.0
28
-
29
- # Safetensors - Faster model loading
30
  safetensors>=0.4.0
31
 
32
- # ==================== System Utilities ====================
33
- # Requests - HTTP library (backup dependencies)
34
- requests>=2.31.0
35
-
36
  # ==================== Notes ====================
37
- # - Total install size: ~2-3GB (PyTorch + models)
38
- # - Works on CPU and GPU (CUDA/MPS)
39
- # - Optimized for Hugging Face Spaces free tier
40
- # - No external API keys required - everything runs locally
41
- # - Models auto-download from Hugging Face Hub on first run
 
1
+ # Food Recognition API - FastAPI Backend
2
+ # Optimized for Hugging Face Spaces
3
 
4
+ # ==================== Core API Framework ====================
5
+ fastapi>=0.104.0
6
+ uvicorn[standard]>=0.24.0
7
+ python-multipart>=0.0.6
8
 
9
+ # ==================== Deep Learning ====================
10
  torch>=2.6.0
11
  torchvision>=0.20.0
 
 
12
  transformers>=4.35.0
13
 
 
 
 
 
14
  # ==================== Image Processing ====================
 
15
  Pillow>=10.0.0
16
+ numpy>=1.24.0,<2.0.0
17
 
18
+ # ==================== Optimizations ====================
 
 
 
 
19
  accelerate>=0.20.0
 
 
20
  safetensors>=0.4.0
21
 
 
 
 
 
22
  # ==================== Notes ====================
23
+ # Model: nateraw/food (Food-101 pretrained)
24
+ # Total size: ~2-3GB (PyTorch + model)
25
+ # API endpoint: POST /api/analyze-food
26
+ # CORS: Enabled for Next.js