Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- Dockerfile +27 -0
- fastapi_app.py +58 -0
- llm_backend.py +51 -0
- pyproject.toml +12 -0
Dockerfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use a lightweight Python image
|
| 2 |
+
FROM python:3.12-slim
|
| 3 |
+
|
| 4 |
+
# Install uv from the official image
|
| 5 |
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
| 6 |
+
|
| 7 |
+
# Set working directory
|
| 8 |
+
WORKDIR /app
|
| 9 |
+
|
| 10 |
+
# Copy pyproject.toml to install dependencies first (caching)
|
| 11 |
+
COPY pyproject.toml .
|
| 12 |
+
|
| 13 |
+
# Install dependencies using uv sync
|
| 14 |
+
# This creates a virtual environment at /app/.venv
|
| 15 |
+
RUN uv sync --no-install-project
|
| 16 |
+
|
| 17 |
+
# Add the virtual environment to the PATH
|
| 18 |
+
ENV PATH="/app/.venv/bin:$PATH"
|
| 19 |
+
|
| 20 |
+
# Copy the rest of the application code
|
| 21 |
+
COPY . .
|
| 22 |
+
|
| 23 |
+
# Expose the port the app runs on
|
| 24 |
+
EXPOSE 8000
|
| 25 |
+
|
| 26 |
+
# Command to run the application
|
| 27 |
+
CMD ["uvicorn", "fastapi_app:app", "--host", "0.0.0.0", "--port", "8000"]
|
fastapi_app.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#fastapi_app.py
|
| 2 |
+
from fastapi import FastAPI, HTTPException
|
| 3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
from llm_backend import process_shelf_image
|
| 6 |
+
import uvicorn
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
app = FastAPI(title="Retail Shelf Analyzer API")
|
| 10 |
+
|
| 11 |
+
# Add CORS middleware
|
| 12 |
+
app.add_middleware(
|
| 13 |
+
CORSMiddleware,
|
| 14 |
+
allow_origins=["*"], # Allows all origins
|
| 15 |
+
allow_credentials=True,
|
| 16 |
+
allow_methods=["*"], # Allows all methods
|
| 17 |
+
allow_headers=["*"], # Allows all headers
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
class ImageRequest(BaseModel):
|
| 21 |
+
image_url: str
|
| 22 |
+
|
| 23 |
+
@app.get("/", summary="Health Check", tags=["System"])
|
| 24 |
+
def read_root():
|
| 25 |
+
"""
|
| 26 |
+
Checks if the API is running and reachable.
|
| 27 |
+
|
| 28 |
+
Returns:
|
| 29 |
+
dict: A simple message confirming the API status.
|
| 30 |
+
"""
|
| 31 |
+
return {"message": "Retail Shelf Analyzer API is running"}
|
| 32 |
+
|
| 33 |
+
@app.post("/analyze_shelf", summary="Analyze Retail Shelf Image", tags=["Shelf Analysis"])
|
| 34 |
+
def analyze_shelf(request: ImageRequest):
|
| 35 |
+
"""
|
| 36 |
+
Analyzes a retail shelf image to extract product information.
|
| 37 |
+
|
| 38 |
+
This endpoint accepts an image URL, processes it using a Generative AI model,
|
| 39 |
+
and returns a structured Markdown table containing:
|
| 40 |
+
- **ID**: Unique identifier for each item.
|
| 41 |
+
- **Product_SKU**: Identified product name or type.
|
| 42 |
+
- **Shelf_ID**: Shelf location identifier.
|
| 43 |
+
- **Last_Updated**: Timestamp of the analysis.
|
| 44 |
+
|
| 45 |
+
If the image is unclear, it returns an error message requesting a re-upload.
|
| 46 |
+
"""
|
| 47 |
+
try:
|
| 48 |
+
# Validate URL (basic check)
|
| 49 |
+
if not request.image_url:
|
| 50 |
+
raise HTTPException(status_code=400, detail="Image URL is required")
|
| 51 |
+
|
| 52 |
+
markdown_output = process_shelf_image(request.image_url)
|
| 53 |
+
return {"markdown_output": markdown_output}
|
| 54 |
+
except Exception as e:
|
| 55 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 56 |
+
|
| 57 |
+
if __name__ == "__main__":
|
| 58 |
+
uvicorn.run("fastapi_app:app", host="0.0.0.0", port=8000, reload=True)
|
llm_backend.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#llm_backend.py
|
| 2 |
+
from langchain.chat_models import init_chat_model
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
from langchain_core.messages import HumanMessage
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
# Initialize the model
|
| 10 |
+
llm = init_chat_model("google_genai:gemini-2.5-flash")
|
| 11 |
+
|
| 12 |
+
def process_shelf_image(image_url: str):
|
| 13 |
+
current_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 14 |
+
# Create the HumanMessage
|
| 15 |
+
message = HumanMessage(
|
| 16 |
+
content=[
|
| 17 |
+
{
|
| 18 |
+
"type": "text",
|
| 19 |
+
"text": f"""You are an Expert Retail Inventory Manager.
|
| 20 |
+
Analyze the provided image of a retail shelf and extract the product information into a structured Markdown table.
|
| 21 |
+
|
| 22 |
+
The table must have the following columns:
|
| 23 |
+
| ID | Product_SKU | Shelf_ID | Last_Updated |
|
| 24 |
+
|
| 25 |
+
Rules for extraction:
|
| 26 |
+
- **ID**: Generate a unique identifier for each distinct item (e.g., ITEM_001, ITEM_002).
|
| 27 |
+
- **Product_SKU**: Identify the product name or type visible on the label or packaging. Be as specific as possible.
|
| 28 |
+
- **Shelf_ID**: Identify the shelf number or location code if visible. If not explicitly visible, assign a logical identifier (e.g., SHELF_01).
|
| 29 |
+
- **Last_Updated**: {current_timestamp}
|
| 30 |
+
|
| 31 |
+
**Exception Handling:**
|
| 32 |
+
- If the image is very blurry, too dark, or the product details are not legible for many of the products to accurately identify the SKU, DO NOT output the table. If only few products labels are not legible, output the table for the products that are legible.
|
| 33 |
+
- Instead, output a single line message: "Error: Unable to identify products. Please re-upload a clearer image."
|
| 34 |
+
|
| 35 |
+
Output ONLY the markdown table or the error message. Do not include any other text."""
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"type": "image",
|
| 39 |
+
"url": image_url
|
| 40 |
+
},
|
| 41 |
+
]
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
# Invoke the model
|
| 45 |
+
response = llm.invoke([message])
|
| 46 |
+
return response.content
|
| 47 |
+
|
| 48 |
+
if __name__ == "__main__":
|
| 49 |
+
# Test with a default image if run directly
|
| 50 |
+
test_url = "https://drive.google.com/uc?export=download&id=1MyH2a4rgjYjnPcmV8W-7k4vjCRIaZo8y"
|
| 51 |
+
print(f"Response: {process_shelf_image(test_url)}")
|
pyproject.toml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "backend"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Add your description here"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.12"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"fastapi[standard]>=0.122.0",
|
| 9 |
+
"langchain[google-genai]>=1.1.0",
|
| 10 |
+
"pillow>=12.0.0",
|
| 11 |
+
"python-dotenv>=1.2.1",
|
| 12 |
+
]
|