ShelfEye / app.py
rishavk77's picture
Upload folder using huggingface_hub
0dc87cd verified
Raw History Blame Contribute Delete
4.7 kB
"""ShelfEye Gradio Demo β€” AI-Powered Retail Shelf Intelligence."""
import sys
import json
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "src"))
import gradio as gr
import numpy as np
from detector import detect_products
from cropper import crop_detections
from embedder import embed_catalogue, embed_crops
from matcher import match_detections
from position import assign_positions
from compliance import check_compliance
from visualizer import draw_detections
def analyze_shelf(image_path: str, use_vlm: bool, clip_threshold: float):
if image_path is None:
return None, "Upload a shelf image to begin."
dets = detect_products(image_path)
dets = crop_detections(image_path, dets)
dets = match_detections(dets, clip_threshold=clip_threshold, use_vlm=use_vlm)
dets = assign_positions(dets)
annotated = draw_detections(image_path, dets)
annotated_rgb = annotated[:, :, ::-1]
# Build pipeline result for compliance
products = []
for d in dets:
products.append({
"sku_id": d.get("sku_id"),
"product_name": d.get("product_name", "unknown"),
"confidence": round(d.get("similarity", d.get("confidence", 0)), 4),
"bbox": [int(c) for c in d["bbox"]],
"row": d["row"],
"column": d["column"],
"matched": d.get("matched", False),
"match_method": d.get("match_method", "none"),
})
matched_count = sum(1 for p in products if p["matched"])
result = {
"image": Path(image_path).name,
"total_products": len(products),
"matched": matched_count,
"unmatched": len(products) - matched_count,
"products": products,
}
# Compliance check
planogram_path = Path("catalogue/planogram.json")
if planogram_path.exists():
compliance = check_compliance(result, str(planogram_path))
else:
compliance = {"note": "No planogram found. Skipping compliance check."}
result_json = json.dumps(result, indent=2)
compliance_json = json.dumps(compliance, indent=2)
summary = (
f"**Detected:** {len(products)} products\n\n"
f"**Matched:** {matched_count} ({matched_count/len(products)*100:.0f}%)\n\n"
f"**Unmatched:** {len(products) - matched_count}\n\n"
)
if "compliance_score" in compliance:
summary += (
f"**Compliance Score:** {compliance['compliance_score']}%\n\n"
f"**Out of Stock:** {compliance['summary']['out_of_stock']}\n\n"
f"**Wrong Position:** {compliance['summary']['wrong_position']}\n\n"
f"**Unexpected:** {compliance['summary']['unexpected']}"
)
return annotated_rgb, summary, result_json, compliance_json
with open("catalogue/catalogue.json") as f:
catalogue = json.load(f)
catalogue_info = "\n".join(f"- {p['product_name']} ({p['brand']})" for p in catalogue)
with gr.Blocks(title="ShelfEye β€” AI Shelf Intelligence", theme=gr.themes.Soft()) as demo:
gr.Markdown(
"# ShelfEye β€” AI-Powered Retail Shelf Intelligence\n"
"Upload a retail shelf image to detect products, match them against a catalogue, "
"and check planogram compliance.\n\n"
"**Pipeline:** Grounding DINO (detection) β†’ CLIP (matching) β†’ Qwen2.5-VL (fallback) β†’ Compliance Engine"
)
with gr.Row():
with gr.Column(scale=1):
input_image = gr.Image(type="filepath", label="Upload Shelf Image")
with gr.Accordion("Settings", open=False):
use_vlm = gr.Checkbox(value=True, label="Enable VLM Fallback (Qwen2.5-VL)")
clip_threshold = gr.Slider(0.5, 0.9, value=0.65, step=0.05, label="CLIP Match Threshold")
run_btn = gr.Button("Analyze Shelf", variant="primary")
gr.Markdown("### Catalogue (20 products)")
gr.Markdown(catalogue_info)
with gr.Column(scale=2):
output_image = gr.Image(label="Annotated Result")
summary_md = gr.Markdown(label="Summary")
with gr.Row():
result_json = gr.Code(label="Detection Result (JSON)", language="json")
compliance_json = gr.Code(label="Compliance Report (JSON)", language="json")
run_btn.click(
fn=analyze_shelf,
inputs=[input_image, use_vlm, clip_threshold],
outputs=[output_image, summary_md, result_json, compliance_json],
)
gr.Markdown(
"---\n"
"Built with Grounding DINO, CLIP, Qwen2.5-VL | "
"[GitHub](https://github.com/rixav77/ShelfEye)"
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)