A FastAPI service that classifies food images, estimates calories, and returns a simple ingredient breakdown for the detected dish.
- Image upload API for food classification
- TensorFlow model integration with a fallback prediction mode if the model cannot be loaded
- Calorie estimates for the supported food classes
- Saved upload tracking in the
uploads/folder - CORS enabled for frontend integration
- Portion size adjustment - Calculate calories for different portion sizes
- Healthier alternatives - Get healthier food suggestions with calorie comparisons
- Batch processing - Process multiple food images at once
The current model is set up for these labels:
- pizza
- burger
- dosa
- rice
- salad
- icecream
- biriyani
- pasta
- noodles
- chicken
- fish
- fries
- sandwich
- taco
- samosa
- momos
- paneer
- tandoori_chicken
- sushi
- steak
- cake
- waffle
- oats
- nuts
app/- FastAPI application codeapp/main.py- API routes and upload handlingapp/model_loader.py- model loading and prediction helpersapp/config.py- class labels, ingredients, and calorie dataapp/utils.py- image preprocessing utilitiesapp/routers/predict.py- prediction endpointsapp/services/prediction_service.py- prediction and calculation servicesapp/schemas.py- Pydantic models for request/response validationmodel/- trained model artifactsdataset/- image dataset organized by food classuploads/- saved uploaded imagesrun.py- local development entry pointtrain_model.py- model training scriptdownload_images.py- image collection helperpopulate_dataset.py- dataset preparation scriptclean_dataset.py- dataset cleanup scripttopup_biriyani.py- class-specific dataset helpertopup_icecream.py- class-specific dataset helper
Install the Python dependencies listed in requirements.txt:
- fastapi
- uvicorn
- python-multipart
- numpy
- pillow
- tensorflow
- icrawler
- Create and activate a virtual environment.
- Install dependencies:
pip install -r requirements.txt- Make sure the trained model exists at
model/model.h5.
Start the development server with:
python run.pyBy default, the API runs at http://0.0.0.0:8000.
POST /predict
Upload a single food image to detect the food, get calories, and nutrition breakdown.
Request: Multipart form with file parameter (image file)
Response Example:
{
"food": "pizza",
"confidence": 0.92,
"ingredients": ["pizza base", "cheese", "tomato sauce", "vegetables"],
"ingredient_predictions": [],
"ingredient_source": "config_fallback",
"calories": 320.0,
"nutrition": {
"totals": {
"calories": 320.0,
"protein": 14.2,
"carbs": 47.0,
"fat": 12.4
},
"by_ingredient": [...]
},
"saved_file": "uploads/20260501_143022_abc123.jpg",
"cache_hit": false
}POST /adjust-portion
Calculate calories for a different portion size.
Request:
{
"original_calories": 320.0,
"portion_multiplier": 0.5,
"description": "half portion"
}Response:
{
"original_calories": 320.0,
"portion_multiplier": 0.5,
"adjusted_calories": 160.0,
"description": "half portion",
"message": "Adjusted from 320.0kcal to 160.0kcal"
}GET /alternatives/{food}?calories={calories}
Get healthier food alternatives with calorie comparison.
Parameters:
food: Food name (e.g., "pizza", "burger", "fries")calories: Original calorie count (optional)
Response Example:
{
"detected_food": "pizza",
"detected_food_calories": 320.0,
"alternatives": [
{
"name": "Cauliflower crust pizza",
"calories": 150,
"reduction_percent": 40,
"benefits": ["Lower carbs", "More fiber", "Lower calories"]
},
{
"name": "Whole wheat pizza",
"calories": 220,
"reduction_percent": 25,
"benefits": ["More fiber", "Complex carbs"]
}
],
"message": "Found 2 healthier alternatives for pizza"
}POST /predict-batch
Process up to 10 food images at once.
Request: Multipart form with multiple files parameter
Response Example:
{
"total_images": 3,
"successful_predictions": 3,
"failed_predictions": 0,
"results": [
{
"file_name": "pizza.jpg",
"food": "pizza",
"confidence": 0.92,
"calories": 320.0,
"nutrition": {...},
"success": true,
"error": ""
},
{
"file_name": "burger.jpg",
"food": "burger",
"confidence": 0.88,
"calories": 370.0,
"nutrition": {...},
"success": true,
"error": ""
}
],
"total_calories": 690.0,
"average_confidence": 0.90
}The following foods have healthier alternatives configured:
- Pizza → Cauliflower crust pizza, Whole wheat pizza
- Burger → Grilled chicken burger, Veggie burger
- Fries → Baked sweet potato fries, Air-fried regular fries
- Ice cream → Greek yogurt frozen dessert, Fruit sorbet
- Cake → Almond flour cake, Whole wheat cake
- Pasta → Whole wheat pasta, Zucchini noodles
- Noodles → Brown rice noodles, Vegetable noodles
- Samosa → Baked samosa, Vegetable spring roll
- Waffle → Whole grain waffle, Protein waffle
- Steak → Lean grilled steak, Grilled salmon
- Sandwich → Whole wheat sandwich, Lettuce wrap sandwich
Train dish classifier:
python train_model.pycurl -X POST -F "file=@pizza.jpg" http://localhost:8000/predictcurl -X POST -F "files=@pizza.jpg" -F "files=@burger.jpg" http://localhost:8000/predict-batchcurl -X POST -H "Content-Type: application/json" \
-d '{"original_calories": 320, "portion_multiplier": 0.5, "description": "half portion"}' \
http://localhost:8000/adjust-portioncurl "http://localhost:8000/alternatives/pizza?calories=320"Train ingredient detector (multi-label):
python train_ingredient_model.pyBefore running ingredient training, create dataset/ingredient_annotations.json using true per-image labels.
Example format:
[
{
"image": "salad/salad_001.jpg",
"labels": ["lettuce", "tomato", "cucumber", "carrot", "salt", "black pepper"]
},
{
"image": "biriyani/biriyani_001.jpg",
"labels": ["rice", "onion", "tomato", "ginger", "garlic", "chili", "turmeric"]
}
]imageis the relative path underdataset/labelsare the actual ingredients visible/used for that image- minimum recommended size is 500+ labeled images
Health check endpoint.
Response:
{
"message": "Food Calorie API Running"
}Upload an image file using multipart form data with the field name file.
Example using curl:
curl -X POST "http://127.0.0.1:8000/predict" -F "file=@sample.jpg"Successful response example:
{
"food": "pizza",
"ingredients": ["pizza base", "cheese", "tomato sauce", "vegetables"],
"ingredient_predictions": [
{"name": "cheese", "confidence": 0.91},
{"name": "tomato sauce", "confidence": 0.84}
],
"ingredient_source": "ingredient_model",
"calories": 320,
"nutrition": {
"totals": {"calories": 320.0, "protein": 13.2, "carbs": 47.0, "fat": 10.4},
"by_ingredient": [
{"name": "pizza base", "calories": 180.0, "protein": 6.0, "carbs": 34.0, "fat": 4.0},
{"name": "cheese", "calories": 80.0, "protein": 5.0, "carbs": 1.0, "fat": 6.0}
]
},
"confidence": 0.92,
"saved_file": "uploads/20260423_120000_123456_abcd1234.jpg",
"cache_hit": false
}If the uploaded image cannot be processed, the API returns:
{
"error": "Invalid image file",
"saved_file": "uploads/..."
}If the uploaded file is invalid, the API returns HTTP 400 with a structured error JSON.
If the same image is uploaded repeatedly within the cache TTL window, cache_hit becomes true for faster responses.
- Uploaded files are saved automatically in
uploads/. - If
model/model.h5is missing or cannot be loaded, the app falls back to random predictions so the service still runs. - The calorie values are defined in
app/config.pyand can be adjusted there.