File size: 4,695 Bytes
0dc87cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""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)