Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from ultralytics import YOLO | |
| import PIL.Image | |
| import numpy as np | |
| from typing import List, Tuple, Dict, Optional | |
| from huggingface_hub import InferenceClient | |
| # Load the trained model | |
| model = YOLO('best.pt') | |
| # Initialize state structure | |
| def init_user_state() -> Dict: | |
| """Initialize the user state dictionary.""" | |
| return { | |
| 'name': '', | |
| 'age': None, | |
| 'weight_lbs': None, | |
| 'height_cm': None, | |
| 'gender': '', | |
| 'activity_level': '', | |
| 'goal': '', | |
| 'calorie_target': None, | |
| 'cuisine_preference': '', | |
| 'detected_ingredients': [], | |
| 'ingredient_list_text': '' | |
| } | |
| # ==================== BMR & CALORIE CALCULATION ==================== | |
| def convert_height_to_cm(height_ft: Optional[float], height_in: Optional[float]) -> Optional[float]: | |
| """Convert feet and inches to centimeters.""" | |
| if height_ft is None or height_in is None: | |
| return None | |
| total_inches = (height_ft * 12) + height_in | |
| return total_inches * 2.54 | |
| def calculate_bmr(weight_kg: float, height_cm: float, age: int, gender: str) -> float: | |
| """ | |
| Calculate Basal Metabolic Rate using Mifflin-St Jeor Equation. | |
| BMR (Men) = 10 ร weight(kg) + 6.25 ร height(cm) - 5 ร age(years) + 5 | |
| BMR (Women) = 10 ร weight(kg) + 6.25 ร height(cm) - 5 ร age(years) - 161 | |
| """ | |
| base_bmr = (10 * weight_kg) + (6.25 * height_cm) - (5 * age) | |
| if gender.lower() == 'male': | |
| bmr = base_bmr + 5 | |
| else: # female | |
| bmr = base_bmr - 161 | |
| return bmr | |
| def get_activity_multiplier(activity_level: str) -> float: | |
| """Get activity multiplier based on activity level.""" | |
| multipliers = { | |
| 'Sedentary': 1.2, | |
| 'Light': 1.375, | |
| 'Moderate': 1.55, | |
| 'Active': 1.725, | |
| 'Very Active': 1.9 | |
| } | |
| return multipliers.get(activity_level, 1.2) | |
| def get_goal_adjustment(goal: str) -> int: | |
| """Get calorie adjustment based on goal.""" | |
| adjustments = { | |
| 'Cutting': -500, | |
| 'Maintain': 0, | |
| 'Bulking': +500, | |
| 'Custom': 0 # Will be handled separately | |
| } | |
| return adjustments.get(goal, 0) | |
| def calculate_calorie_target( | |
| weight_lbs: Optional[float], | |
| height_ft: Optional[float], | |
| height_in: Optional[float], | |
| age: Optional[int], | |
| gender: Optional[str], | |
| activity_level: Optional[str], | |
| goal: Optional[str], | |
| custom_calories: Optional[float], | |
| state: Dict | |
| ) -> Tuple[Dict, str]: | |
| """ | |
| Calculate daily calorie target based on user inputs. | |
| Updates state and returns formatted result. | |
| """ | |
| # Validate inputs | |
| if not all([weight_lbs, height_ft is not None, height_in is not None, age, gender, activity_level, goal]): | |
| return state, "**Please fill in all required fields.**" | |
| # Convert weight to kg | |
| weight_kg = weight_lbs * 0.453592 | |
| # Convert height to cm | |
| height_cm = convert_height_to_cm(height_ft, height_in) | |
| if height_cm is None: | |
| return state, "**Please enter valid height values.**" | |
| # Calculate BMR | |
| bmr = calculate_bmr(weight_kg, height_cm, age, gender) | |
| # Get activity multiplier | |
| activity_mult = get_activity_multiplier(activity_level) | |
| # Calculate TDEE (Total Daily Energy Expenditure) | |
| tdee = bmr * activity_mult | |
| # Apply goal adjustment | |
| if goal == 'Custom' and custom_calories is not None: | |
| calorie_target = custom_calories | |
| else: | |
| goal_adj = get_goal_adjustment(goal) | |
| calorie_target = tdee + goal_adj | |
| # Update state | |
| state['weight_lbs'] = weight_lbs | |
| state['height_cm'] = height_cm | |
| state['age'] = age | |
| state['gender'] = gender | |
| state['activity_level'] = activity_level | |
| state['goal'] = goal | |
| state['calorie_target'] = calorie_target | |
| # Format output | |
| result_text = f""" | |
| ## ๐ Your Daily Calorie Target | |
| **BMR (Basal Metabolic Rate):** {bmr:.0f} calories/day | |
| **Activity Level:** {activity_level} (ร{activity_mult:.2f}) | |
| **TDEE (Total Daily Energy Expenditure):** {tdee:.0f} calories/day | |
| **Goal Adjustment:** {get_goal_adjustment(goal):+.0f} calories | |
| ### ๐ฏ **Daily Calorie Target: {calorie_target:.0f} calories** | |
| *This target is based on your profile and has been saved for recipe generation.* | |
| """ | |
| return state, result_text | |
| # ==================== INGREDIENT DETECTION ==================== | |
| def detect_ingredients(images: List, state: Dict) -> Tuple[Dict, List, str]: | |
| """ | |
| Process multiple images and return detected ingredients. | |
| Also updates the state with detected ingredients. | |
| Args: | |
| images: List of uploaded images (file paths) | |
| state: User state dictionary | |
| Returns: | |
| Tuple of (updated_state, processed_images, ingredient_list_text) | |
| """ | |
| if not images or len(images) == 0: | |
| return state, [], "**No images uploaded.**" | |
| processed_images = [] | |
| all_detected_items = set() | |
| # Process each uploaded image | |
| for image_file in images: | |
| if image_file is None: | |
| continue | |
| # Get file path (Gradio File component returns file objects) | |
| image_path = image_file.name if hasattr(image_file, 'name') else image_file | |
| # Run prediction with your local settings (conf=0.7) | |
| results = model.predict(source=image_path, conf=0.7, iou=0.3, verbose=False) | |
| # Get the image with bounding boxes drawn | |
| result_image = results[0].plot() | |
| # Extract detected ingredients from this image | |
| for box in results[0].boxes: | |
| class_id = int(box.cls) | |
| class_name = model.names[class_id] | |
| all_detected_items.add(class_name) | |
| # Convert numpy array to PIL Image for display | |
| # YOLO returns BGR, convert to RGB | |
| if len(result_image.shape) == 3: | |
| result_image_rgb = result_image[..., ::-1] # BGR to RGB | |
| processed_images.append(PIL.Image.fromarray(result_image_rgb)) | |
| else: | |
| processed_images.append(PIL.Image.fromarray(result_image)) | |
| # Create formatted ingredient list | |
| if all_detected_items: | |
| ingredient_list = sorted(list(all_detected_items)) | |
| ingredient_list_text = "**Detected Ingredients:**\n\n" | |
| ingredient_list_text += "\n".join([f"โข {item.capitalize()}" for item in ingredient_list]) | |
| ingredient_list_text += f"\n\n**Total unique items:** {len(ingredient_list)}" | |
| # Update state with detected ingredients | |
| state['detected_ingredients'] = ingredient_list | |
| state['ingredient_list_text'] = ingredient_list_text | |
| else: | |
| ingredient_list_text = "**No ingredients detected.**\n\nTry adjusting the image quality or lighting." | |
| state['detected_ingredients'] = [] | |
| state['ingredient_list_text'] = ingredient_list_text | |
| return state, processed_images, ingredient_list_text | |
| # ==================== RECIPE GENERATION ==================== | |
| def generate_recipes(cuisine_preference: Optional[str], state: Dict) -> Tuple[Dict, str]: | |
| """ | |
| Generate recipes using LLM based on user profile and detected ingredients. | |
| """ | |
| # Validate that we have the necessary data | |
| if not state.get('calorie_target'): | |
| return state, "**โ ๏ธ Please complete your User Profile & Goals first to set your calorie target.**" | |
| if not state.get('detected_ingredients'): | |
| return state, "**โ ๏ธ Please scan ingredients in the Ingredient Scanner tab first.**" | |
| if not cuisine_preference: | |
| return state, "**โ ๏ธ Please select a cuisine preference.**" | |
| # Update state | |
| state['cuisine_preference'] = cuisine_preference | |
| # Get user data | |
| calorie_target = int(state['calorie_target']) | |
| goal = state.get('goal', 'Maintain') | |
| ingredients = state['detected_ingredients'] | |
| ingredient_list = ", ".join([item.capitalize() for item in ingredients]) | |
| # Map goal to dietary focus | |
| goal_descriptions = { | |
| 'Cutting': 'weight loss and calorie deficit', | |
| 'Maintain': 'maintaining current weight', | |
| 'Bulking': 'muscle gain with high protein', | |
| 'Custom': 'your custom calorie target' | |
| } | |
| goal_desc = goal_descriptions.get(goal, 'your goals') | |
| # Construct prompt | |
| prompt = f"""You are a professional nutritionist and chef. Create 3 distinct, detailed recipes that: | |
| 1. Use these available ingredients: {ingredient_list} | |
| 2. Fit within a daily calorie target of approximately {calorie_target} calories per day | |
| 3. Match {cuisine_preference} cuisine style | |
| 4. Align with the goal of {goal_desc} | |
| For each recipe, provide: | |
| - Recipe name | |
| - Serving size | |
| - Estimated calories per serving | |
| - Complete ingredient list (you may suggest additional common pantry items if needed) | |
| - Step-by-step cooking instructions | |
| - Nutritional highlights relevant to the goal | |
| Format each recipe clearly with headers. Make the recipes practical, delicious, and suitable for home cooking.""" | |
| try: | |
| # Use Hugging Face Inference API | |
| import os | |
| # Try multiple ways to get the token | |
| hf_token = None | |
| # Method 1: Check HF_TOKEN environment variable | |
| hf_token = os.getenv("HF_TOKEN", None) | |
| # Method 2: Check HUGGING_FACE_HUB_TOKEN (alternative name) | |
| if not hf_token: | |
| hf_token = os.getenv("HUGGING_FACE_HUB_TOKEN", None) | |
| # Method 3: Try to get from Hugging Face cache (for Spaces or logged-in users) | |
| if not hf_token: | |
| try: | |
| from huggingface_hub import HfFolder | |
| hf_token = HfFolder.get_token() | |
| except: | |
| pass | |
| if not hf_token: | |
| return state, """**โ ๏ธ Hugging Face Token Required** | |
| Please set your HF_TOKEN environment variable to use recipe generation. | |
| **For Hugging Face Spaces:** | |
| 1. Go to your Space Settings (gear icon) | |
| 2. Scroll to "Repository secrets" | |
| 3. Click "New secret" | |
| 4. Name: `HF_TOKEN` | |
| 5. Value: Your Hugging Face token | |
| 6. Click "Add secret" and restart your Space | |
| **For Local Development (Windows):** | |
| 1. Press Win+R, type `sysdm.cpl`, press Enter | |
| 2. Go to "Advanced" tab โ "Environment Variables" | |
| 3. Under "User variables", click "New" | |
| 4. Variable name: `HF_TOKEN` | |
| 5. Variable value: Your Hugging Face token | |
| 6. Click OK and restart your application | |
| Get your token at: https://huggingface.co/settings/tokens""" | |
| client = InferenceClient(token=hf_token) | |
| # Try using models that support text-generation | |
| # List of models to try in order of preference (all verified to work with text-generation) | |
| models_to_try = [ | |
| "meta-llama/Llama-3.2-3B-Instruct", # Fast and reliable | |
| "meta-llama/Llama-3.1-8B-Instruct", # Better quality | |
| "mistralai/Mistral-7B-Instruct-v0.3", # Alternative option | |
| "microsoft/Phi-3-mini-4k-instruct", # Lightweight fallback | |
| "google/gemma-2-2b-it", # Additional reliable option | |
| ] | |
| response = None | |
| last_error = None | |
| successful_model = None | |
| for model_name in models_to_try: | |
| try: | |
| response = client.text_generation( | |
| prompt, | |
| model=model_name, | |
| max_new_tokens=1500, | |
| temperature=0.7, | |
| ) | |
| successful_model = model_name | |
| break # Success, exit the loop | |
| except Exception as model_error: | |
| last_error = model_error | |
| continue # Try next model | |
| # If all models failed, raise error with details | |
| if response is None: | |
| error_msg = f"All models failed. Last error: {str(last_error)}" | |
| if not hf_token: | |
| error_msg += "\n\n๐ก TIP: Make sure you have set your HF_TOKEN environment variable." | |
| raise Exception(error_msg) | |
| # Extract text if response is a formatted object | |
| if hasattr(response, 'generated_text'): | |
| response_text = response.generated_text | |
| elif isinstance(response, str): | |
| response_text = response | |
| else: | |
| response_text = str(response) | |
| recipes_text = f"""## ๐ณ Recipe Suggestions for {cuisine_preference} Cuisine | |
| **Your Profile:** | |
| - Daily Calorie Target: {calorie_target} calories | |
| - Goal: {goal} | |
| - Available Ingredients: {ingredient_list} | |
| --- | |
| {response_text} | |
| --- | |
| *Recipes generated based on your profile and available ingredients.*""" | |
| return state, recipes_text | |
| except Exception as e: | |
| error_msg = f"""**โ ๏ธ Error generating recipes.** | |
| Please try again. If the issue persists, you may need to: | |
| 1. Check your internet connection | |
| 2. Ensure you have a Hugging Face API token set (if required) | |
| 3. Try a different cuisine preference | |
| Error details: {str(e)}""" | |
| return state, error_msg | |
| # ==================== GRADIO INTERFACE ==================== | |
| # Custom CSS for a modern, clean interface | |
| custom_css = """ | |
| .gradio-container { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| .main-header { | |
| text-align: center; | |
| padding: 20px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border-radius: 10px; | |
| margin-bottom: 20px; | |
| } | |
| .description-box { | |
| background: #f8f9fa; | |
| padding: 15px; | |
| border-radius: 8px; | |
| border-left: 4px solid #667eea; | |
| margin-bottom: 20px; | |
| color: #000000 !important; | |
| } | |
| .description-box * { | |
| color: #000000 !important; | |
| } | |
| .ingredient-list { | |
| background: #ffffff; | |
| padding: 20px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| min-height: 200px; | |
| color: #000000 !important; | |
| } | |
| .ingredient-list * { | |
| color: #000000 !important; | |
| } | |
| .calorie-result { | |
| background: #e8f5e9; | |
| padding: 20px; | |
| border-radius: 8px; | |
| border-left: 4px solid #4caf50; | |
| margin-top: 20px; | |
| color: #000000 !important; | |
| } | |
| .calorie-result * { | |
| color: #000000 !important; | |
| } | |
| """ | |
| # Create the Gradio interface | |
| with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo: | |
| # Header | |
| gr.Markdown( | |
| """ | |
| # ๐ฅ Forked Recipe-Pal | |
| Your AI-powered kitchen companion: Scan ingredients, calculate calories, and generate personalized recipes! | |
| """, | |
| elem_classes=["main-header"] | |
| ) | |
| # Initialize state | |
| user_state = gr.State(value=init_user_state) | |
| # Tab structure | |
| with gr.Tabs() as tabs: | |
| # ========== TAB 1: USER PROFILE & GOALS ========== | |
| with gr.Tab("๐ค User Profile & Goals"): | |
| gr.Markdown( | |
| """ | |
| <div class="description-box"> | |
| <strong>๐ Set up your profile:</strong><br> | |
| Enter your personal information and fitness goals to calculate your daily calorie target. | |
| This will be used to generate personalized recipes. | |
| </div> | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| name_input = gr.Textbox( | |
| label="Name", | |
| placeholder="Enter your name", | |
| value="" | |
| ) | |
| with gr.Row(): | |
| age_input = gr.Number( | |
| label="Age", | |
| minimum=1, | |
| maximum=120, | |
| value=None, | |
| precision=0 | |
| ) | |
| gender_input = gr.Dropdown( | |
| label="Gender", | |
| choices=["Male", "Female"], | |
| value=None | |
| ) | |
| with gr.Row(): | |
| weight_input = gr.Number( | |
| label="Weight (lbs)", | |
| minimum=1, | |
| maximum=1000, | |
| value=None, | |
| precision=1 | |
| ) | |
| with gr.Row(): | |
| height_ft_input = gr.Number( | |
| label="Height (feet)", | |
| minimum=1, | |
| maximum=8, | |
| value=None, | |
| precision=0 | |
| ) | |
| height_in_input = gr.Number( | |
| label="Height (inches)", | |
| minimum=0, | |
| maximum=11, | |
| value=None, | |
| precision=0 | |
| ) | |
| activity_input = gr.Dropdown( | |
| label="Activity Level", | |
| choices=["Sedentary", "Light", "Moderate", "Active", "Very Active"], | |
| value=None, | |
| info="Sedentary: Little/no exercise | Light: Light exercise 1-3 days/week | Moderate: Moderate exercise 3-5 days/week | Active: Hard exercise 6-7 days/week | Very Active: Very hard exercise, physical job" | |
| ) | |
| goal_input = gr.Radio( | |
| label="Goal", | |
| choices=["Cutting", "Maintain", "Bulking", "Custom"], | |
| value=None | |
| ) | |
| custom_calories_input = gr.Number( | |
| label="Custom Calorie Target", | |
| minimum=800, | |
| maximum=5000, | |
| value=None, | |
| precision=0, | |
| visible=False, | |
| info="Enter your desired daily calorie target" | |
| ) | |
| calculate_btn = gr.Button( | |
| "๐ Calculate Calorie Target", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| with gr.Column(scale=1): | |
| calorie_output = gr.Markdown( | |
| label="Calorie Calculation Result", | |
| elem_classes=["calorie-result"] | |
| ) | |
| # Show/hide custom calories input based on goal selection | |
| def toggle_custom_calories(goal): | |
| if goal == "Custom": | |
| return gr.update(visible=True) | |
| else: | |
| # Reset value to None when hiding to prevent validation errors | |
| return gr.update(visible=False, value=None) | |
| goal_input.change( | |
| fn=toggle_custom_calories, | |
| inputs=goal_input, | |
| outputs=custom_calories_input | |
| ) | |
| # Calculate calories | |
| calculate_btn.click( | |
| fn=calculate_calorie_target, | |
| inputs=[ | |
| weight_input, | |
| height_ft_input, | |
| height_in_input, | |
| age_input, | |
| gender_input, | |
| activity_input, | |
| goal_input, | |
| custom_calories_input, | |
| user_state | |
| ], | |
| outputs=[user_state, calorie_output] | |
| ) | |
| # Update name in state when changed | |
| name_input.change( | |
| fn=lambda name, state: ({**state, 'name': name}, state), | |
| inputs=[name_input, user_state], | |
| outputs=[user_state, user_state] | |
| ) | |
| # ========== TAB 2: INGREDIENT SCANNER ========== | |
| with gr.Tab("๐ธ Ingredient Scanner"): | |
| gr.Markdown( | |
| """ | |
| <div class="description-box"> | |
| <strong>๐ธ How to use:</strong><br> | |
| 1. Click "Upload Images" or drag and drop multiple photos<br> | |
| 2. Wait for the AI to analyze your ingredients<br> | |
| 3. View all processed images with detection boxes and the complete ingredient list<br> | |
| 4. Detected ingredients will be saved for recipe generation | |
| </div> | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| image_input = gr.File( | |
| file_count="multiple", | |
| file_types=["image"], | |
| label="๐ Upload Images", | |
| height=200 | |
| ) | |
| process_btn = gr.Button( | |
| "๐ Detect Ingredients", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| gr.Markdown("---") | |
| ingredient_output = gr.Markdown( | |
| label="๐ Detected Ingredients", | |
| elem_classes=["ingredient-list"] | |
| ) | |
| with gr.Column(scale=2): | |
| gallery_output = gr.Gallery( | |
| label="๐ผ๏ธ Processed Images with Detections", | |
| show_label=True, | |
| elem_id="gallery", | |
| columns=2, | |
| rows=2, | |
| height="auto", | |
| allow_preview=True, | |
| preview=True | |
| ) | |
| # Process images when button is clicked | |
| process_btn.click( | |
| fn=detect_ingredients, | |
| inputs=[image_input, user_state], | |
| outputs=[user_state, gallery_output, ingredient_output] | |
| ) | |
| # Also process when images are uploaded (auto-detect) | |
| image_input.upload( | |
| fn=detect_ingredients, | |
| inputs=[image_input, user_state], | |
| outputs=[user_state, gallery_output, ingredient_output] | |
| ) | |
| # ========== TAB 3: RECIPE GENERATOR ========== | |
| with gr.Tab("๐ณ Recipe Generator"): | |
| gr.Markdown( | |
| """ | |
| <div class="description-box"> | |
| <strong>๐ณ Generate personalized recipes:</strong><br> | |
| Based on your calorie target, fitness goals, and detected ingredients, | |
| we'll generate 3 custom recipes tailored to your preferences. | |
| </div> | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| cuisine_input = gr.Dropdown( | |
| label="Cuisine Preference", | |
| choices=["Mexican", "Chinese", "American", "Italian", "Indian", "Japanese", "Mediterranean", "Thai", "French"], | |
| value=None, | |
| info="Select your preferred cuisine style" | |
| ) | |
| generate_btn = gr.Button( | |
| "โจ Generate Recipes", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| gr.Markdown("---") | |
| gr.Markdown( | |
| """ | |
| **๐ Requirements:** | |
| - Complete User Profile & Goals tab | |
| - Scan ingredients in Ingredient Scanner tab | |
| - Select a cuisine preference | |
| """ | |
| ) | |
| with gr.Column(scale=2): | |
| recipe_output = gr.Markdown( | |
| label="Generated Recipes", | |
| elem_classes=["ingredient-list"] | |
| ) | |
| # Generate recipes | |
| generate_btn.click( | |
| fn=generate_recipes, | |
| inputs=[cuisine_input, user_state], | |
| outputs=[user_state, recipe_output] | |
| ) | |
| gr.Markdown( | |
| """ | |
| --- | |
| <div style="text-align: center; color: #666; padding: 20px;"> | |
| <small>Powered by YOLOv11 & AI Recipe Generation | Your smart kitchen assistant!</small> | |
| </div> | |
| """ | |
| ) | |
| # Launch the app | |
| if __name__ == "__main__": | |
| demo.launch() | |