File size: 5,934 Bytes
8d67bdf
 
 
 
 
 
 
 
 
 
cacf836
65ddbdd
72254f9
 
 
 
 
cacf836
72254f9
52468e0
2e84422
f2f5f5c
2e84422
72254f9
 
e231d9b
f2f5f5c
 
 
 
 
 
 
72254f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6599a57
8bf75c7
72254f9
8bf75c7
8d67bdf
72254f9
 
 
 
 
 
 
 
 
 
 
 
cacf836
72254f9
 
e231d9b
72254f9
 
fbe9d58
72254f9
 
9943408
72254f9
 
 
 
 
 
 
 
 
 
65ddbdd
cf46432
72254f9
cf46432
8d67bdf
72254f9
52468e0
72254f9
 
 
 
 
52468e0
72254f9
 
 
52468e0
72254f9
 
52468e0
72254f9
 
 
52468e0
72254f9
 
 
 
 
938215a
72254f9
 
 
938215a
72254f9
938215a
72254f9
 
938215a
72254f9
 
 
 
 
c28e6e8
72254f9
8d67bdf
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
# =====================================================================
# 🛸 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())