# ===================================================================== # ๐Ÿ›ธ HUGGING FACE ZERO-GPU INITIALIZATION (MUST BE FIRST) # ===================================================================== import sys # This forces spaces to load right away if it's installed in the HF container try: import spaces except ImportError: pass import torch import pandas as pd import gradio as gr import cv2 import numpy as np from PIL import Image from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor, BitsAndBytesConfig from peft import PeftModel from qwen_vl_utils import process_vision_info # ===================================================================== # โšก ENGINE INITIALIZATION # ===================================================================== device = "cuda" if torch.cuda.is_available() else "cpu" quantization_config = BitsAndBytesConfig(load_in_8bit=True) base_model = Qwen2_5_VLForConditionalGeneration.from_pretrained( "Qwen/Qwen2.5-VL-7B-Instruct", quantization_config=quantization_config, device_map="auto", torch_dtype=torch.float16 ) model = PeftModel.from_pretrained(base_model, "uttarasawant/qwen2.5-vl-fridge-adapters") processor = AutoProcessor.from_pretrained("Qwen/Qwen2.5-VL-7B-Instruct") def sample_frames_from_video(video_path, num_frames=4): cap = cv2.VideoCapture(video_path) frames = [] total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # Handle empty videos if total_frames <= 0: return [] indices = np.linspace(0, total_frames - 1, num_frames, dtype=int) for i in range(total_frames): ret, frame = cap.read() if not ret: break if i in indices: frames.append(Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))) cap.release() return frames # ===================================================================== # ๐Ÿง  CHEF LOGIC ENGINE # ===================================================================== @spaces.GPU(duration=120) def process_kitchen_operations(media_input, budget, days): # GUARD: Stop if no input provided if media_input is None: return None, None, pd.DataFrame(columns=["Ingredient Asset", "Qty", "Status", "Value"]), "### โš ๏ธ System Idle\nPlease upload an image or video." if isinstance(media_input, str): images = sample_frames_from_video(media_input) if not images: return None, None, pd.DataFrame(), "### โŒ Error\nCould not extract frames from video." else: images = [media_input] chef_prompt = f"Act as a professional chef. Identify ingredients. Create a {days}-day meal plan (budget ${budget}). Output as Markdown Table (Day|Breakfast|Lunch|Dinner). Provide inventory list first." content = [{"type": "image", "image": img} for img in images] content.append({"type": "text", "text": chef_prompt}) messages = [{"role": "system", "content": "You are a professional chef. Only use visible ingredients."}, {"role": "user", "content": content}] text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) image_inputs, _ = process_vision_info(messages) inputs = processor(text=[text], images=image_inputs, padding=True, return_tensors="pt").to(device) generated_ids = model.generate(**inputs, max_new_tokens=400) generated_ids_trimmed = [out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)] generated_text = processor.batch_decode(generated_ids_trimmed, skip_special_tokens=True)[0] food_keywords = ['salmon', 'chicken', 'broccoli', 'lettuce', 'tomato', 'pepper', 'mushroom'] found_items = [f for f in food_keywords if f in generated_text.lower()] df_rows = [[item.title(), "1 Unit", "Fresh", f"${2.50 + (idx*0.5):.2f}"] for idx, item in enumerate(found_items)] df = pd.DataFrame(df_rows or [["None", "-", "-", "$0"]], columns=["Ingredient Asset", "Qty", "Status", "Value"]) return images[0], images[0], df, f"### ๐Ÿ‘จโ€๐Ÿณ Chef's Culinary Blueprint\n{generated_text}" # ===================================================================== # ๐ŸŽจ GRADIO INTERFACE # ===================================================================== with gr.Blocks() as demo: gr.Markdown("# ๐Ÿ›ฐ๏ธ Parallel Plate: Digital Twin Chef Engine") with gr.Tabs(): with gr.TabItem("Upload Image"): img_input = gr.Image(type="pil") with gr.TabItem("Upload Video"): vid_input = gr.Video() # Clear other tab when one is used img_input.change(fn=lambda: None, outputs=vid_input) vid_input.change(fn=lambda: None, outputs=img_input) budget_slider = gr.Slider(5, 100, 25, label="Budget ($)") days_slider = gr.Slider(1, 7, 3, label="Days of Supply") with gr.Row(): scan_btn = gr.Button("๐Ÿš€ Initialize Scan & Recipe Plan", variant="primary") clear_btn = gr.Button("๐Ÿงน Clear") with gr.Row(): orig_display = gr.Image(label="Input Source") processed_display = gr.Image(label="Digital Twin Output") inventory_df = gr.Dataframe(label="Asset Manifest") output_text = gr.Markdown() def clear_interface(): empty_df = pd.DataFrame(columns=["Ingredient Asset", "Qty", "Status", "Value"]) return [None, None, None, None, empty_df, ""] clear_btn.click(fn=clear_interface, inputs=[], outputs=[img_input, vid_input, orig_display, processed_display, inventory_df, output_text]) # Helper to pick the active input def choose_input(img, vid): return vid if vid else img scan_btn.click( fn=lambda img, vid, b, d: process_kitchen_operations(choose_input(img, vid), b, d), inputs=[img_input, vid_input, budget_slider, days_slider], outputs=[orig_display, processed_display, inventory_df, output_text] ) if __name__ == "__main__": demo.launch(theme=gr.themes.Monochrome())