Spaces:
Runtime error
Runtime error
File size: 6,872 Bytes
78ca765 3241931 d978ff7 abf4b12 d978ff7 ccd24c4 800573b bb3429e 34199fb 78ca765 662bc62 d2ab589 662bc62 d2ab589 d55fa75 662bc62 ccd24c4 d55fa75 662bc62 fd71f1c ccd24c4 c008b78 662bc62 ccd24c4 fec93f3 d6d78d7 662bc62 c008b78 662bc62 ab109d6 662bc62 c008b78 662bc62 c008b78 662bc62 c008b78 662bc62 ab109d6 8f27b83 662bc62 c093565 b5cb0f4 aeaf139 b500a85 09544b7 b500a85 ccd24c4 c093565 b5cb0f4 662bc62 c093565 d978ff7 c093565 662bc62 c093565 ab109d6 c093565 d6d78d7 c093565 662bc62 a0e2fc3 d41f80d 70fd80b d41f80d 35a8ef2 8f27b83 b500a85 8f27b83 ab961a7 8f27b83 9ec9edd 8f27b83 800573b aeaf139 ab109d6 ccd24c4 35a8ef2 ccd24c4 35a8ef2 ccd24c4 35a8ef2 ccd24c4 35a8ef2 ab109d6 ccd24c4 ab109d6 97d1026 d6d78d7 97d1026 8abd63c 8f27b83 d6d78d7 3fda78f 97d1026 5e99460 8abd63c 8f27b83 3fda78f 8f27b83 97d1026 78ca765 f595fd8 97d1026 f595fd8 ba96473 5e99460 35a8ef2 d978ff7 ddc33cd |
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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
import gradio as gr
import os
import uuid
import time
from datetime import datetime
from threading import Thread
from google.cloud import storage, bigquery
from fastai.vision.all import load_learner, PILImage
from fastai.vision.augment import Resize
from pathlib import Path
from collections import deque
# Setup GCP credentials
credentials_content = os.environ['gcp_cam']
with open('gcp_key.json', 'w') as f:
f.write(credentials_content)
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'gcp_key.json'
# GCP config
bucket_name = os.environ['gcp_bucket']
pkl_blob = os.environ['pretrained_model']
upload_folder = os.environ['user_data_gcp']
bq_dataset = os.environ['bq_dataset']
bq_table = os.environ['bq_table']
# Load model
local_pkl = Path('cam_meals_f2.pkl')
if not local_pkl.exists():
storage.Client().bucket(bucket_name).blob(pkl_blob).download_to_filename(local_pkl)
learn = load_learner(local_pkl)
bq_client = bigquery.Client()
bucket = storage.Client().bucket(bucket_name)
# Store recent prediction IDs
deferred_feedback = deque(maxlen=100)
# Upload image to GCS
def upload_image_to_gcs(local_path, dest_folder, dest_filename):
blob = bucket.blob(f"{upload_folder}/{dest_folder}{dest_filename}")
blob.upload_from_filename(local_path)
return f"gs://{bucket_name}/{upload_folder}/{dest_folder}{dest_filename}"
# Async BigQuery logging
def log_to_bigquery(record):
table_id = f"{bq_client.project}.{bq_dataset}.{bq_table}"
try:
errors = bq_client.insert_rows_json(table_id, [record])
if errors:
print("BigQuery insert errors:", errors)
except Exception as e:
print("Logging error:", e)
def async_log(record):
Thread(target=log_to_bigquery, args=(record,), daemon=True).start()
# Prediction logic with feedback
def predict(image_path, threshold=0.275, user_feedback=None):
start_time = time.time()
unique_id = str(uuid.uuid4())
timestamp = datetime.utcnow().isoformat()
# Load and resize image using fastai's PILImage
try:
img = PILImage.create(image_path)
img = img.resize((256, 256))
except Exception as e:
print("Image processing error:", e)
return "Image could not be processed."
pred_class, pred_idx, outputs = learn.predict(img)
prob = outputs[pred_idx].item()
dest_folder = f"user_data/{pred_class}/" if prob >= threshold else "user_data/unknown/"
uploaded_gcs_path = upload_image_to_gcs(image_path, dest_folder, f"{unique_id}.jpg")
async_log({
"id": unique_id,
"timestamp": timestamp,
"image_gcs_path": uploaded_gcs_path,
"predicted_class": pred_class,
"confidence": prob,
"threshold": threshold,
"user_feedback": user_feedback or ""
})
deferred_feedback.append((time.time(), unique_id))
print(f"Prediction time: {time.time() - start_time:.2f}s")
return (
f"❓ Unknown Meal: Provide Name. Thanks" if prob <= threshold else
f"⚠️ Meal: {pred_class}, Low Confidence" if 0.275 <= prob <= 0.5 else
f"✅ Meal: {pred_class}"
)
# Feedback-only logic
def submit_feedback_only(feedback_text):
if not feedback_text.strip():
return "⚠️ No feedback provided."
now = time.time()
for ts, uid in reversed(deferred_feedback):
if now - ts <= 120:
async_log({
"id": uid,
"timestamp": datetime.utcnow().isoformat(),
"image_gcs_path": "feedback_only",
"predicted_class": "feedback_update",
"confidence": 0.1,
"threshold": 0.0,
"user_feedback": feedback_text
})
return "✅ Feedback Submitted. Thank you!"
return "⚠️ Feedback not linked: time expired."
# Handle multiple images + feedback
def unified_predict(upload_files, webcam_img, clipboard_img, feedback):
files = []
if upload_files:
files = [file.name for file in upload_files]
elif webcam_img:
files = [webcam_img]
elif clipboard_img:
files = [clipboard_img]
else:
return "No image provided."
return "\n\n".join([predict(f, user_feedback=feedback) for f in files])
# Gradio UI
with gr.Blocks(theme="peach", analytics_enabled=False) as demo:
gr.Markdown("""# Cameroonian Meal Recognizer
<p><b>Welcome to Version 1:</b> Identify traditional Cameroonian dishes from a photo.</p>
<p style='background-color: #b3e5fc; padding: 5px; border-radius: 4px;'>This tool offers a friendly playground to learn about our diverse dishes. Therefore multiple image upload is encouraged for improvement in subsequent versions predictions.</p>
<p><i>Choose an input source below, and our AI will recognize the meal.</i></p>
""")
with gr.Tabs():
with gr.Tab("Upload"):
upload_input = gr.File(file_types=["image"], file_count="multiple", label="Upload Meal Images")
with gr.Tab("Webcam"):
webcam_input = gr.Image(type="filepath", sources=["webcam"], label="Capture from Webcam")
with gr.Tab("Clipboard"):
clipboard_input = gr.Image(type="filepath", sources=["clipboard"], label="Paste from Clipboard")
submit_btn = gr.Button("Identify Meal")
output_box = gr.Textbox(label="Prediction Result", lines=6)
gr.Markdown("### Feedback")
with gr.Row():
feedback_input = gr.Textbox(
label=None,
placeholder="If prediction is wrong, enter correct meal name...",
lines=1,
scale=4
)
feedback_btn = gr.Button("Submit Feedback", scale=1)
feedback_ack = gr.HTML("")
submit_btn.click(
fn=unified_predict,
inputs=[upload_input, webcam_input, clipboard_input, feedback_input],
outputs=output_box
)
def styled_feedback_msg(feedback_text):
msg = submit_feedback_only(feedback_text)
if msg.startswith("✅"):
return f"<span style='color: green; font-weight: bold;'>{msg}</span>"
elif msg.startswith("⚠️"):
return f"<span style='color: orange; font-weight: bold;'>{msg}</span>"
return msg
feedback_btn.click(
fn=styled_feedback_msg,
inputs=feedback_input,
outputs=feedback_ack
)
gr.Markdown("""
<p>Future updates will include:
<ul>
<li>Ingredient lists</li>
<li>Meal preparation details</li>
<li>Origin (locality) info</li>
<li>Nearby restaurants</li>
</ul></p>
<p>Learn more on <a href="https://www.linkedin.com/in/paulinus-jua-21255116b/" target="_blank">Paulinus Jua's LinkedIn</a>.</p>
<p>© 2025 Paulinus Jua. All rights reserved.</p>
""")
if __name__ == "__main__":
print("App setup complete — launching Gradio...")
demo.launch()
print("Launched.")
|