deploy
Deploy Medicine Scanner v1.0.3 β€” Gradio SDK
fa38fe6
"""
MedOS Medicine Scanner β€” HuggingFace Space (Gradio SDK)
Scan medicine labels with AI (Qwen2.5-VL) and return structured JSON.
Uses HF Spaces native Gradio SDK β€” no Docker needed.
"""
import json
import logging
import os
import io
import time
import gradio as gr
import numpy as np
from PIL import Image
from scanner import scan_medicine
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)
def scan_image_ui(image_dict, hf_token: str) -> tuple[str, str]:
"""Gradio interface handler."""
if image_dict is None:
return "Please upload or capture a medicine image.", "{}"
start = time.time()
# gr.Image returns filepath string or numpy array depending on type
if isinstance(image_dict, str):
pil_image = Image.open(image_dict)
elif isinstance(image_dict, np.ndarray):
pil_image = Image.fromarray(image_dict)
else:
pil_image = Image.fromarray(np.asarray(image_dict))
result = scan_medicine(pil_image, hf_token=(hf_token or "").strip())
elapsed = time.time() - start
if not result["success"]:
err = f"Scan failed: {result['error']}"
return err, json.dumps(result, indent=2)
med = result["medicine"]
model_short = (result.get("model_used") or "unknown").split("/")[-1]
lines = [f"Medicine: {med['name']}"]
if med.get("brandName"):
lines.append(f"Brand: {med['brandName']}")
lines.append(f"Dose: {med['dose']}")
lines.append(f"Form: {med['form'].capitalize()}")
if med.get("activeIngredient"):
lines.append(f"Active ingredient: {med['activeIngredient']}")
if med.get("category"):
lines.append(f"Category: {med['category']}")
if med.get("expiryDate"):
lines.append(f"Expiry: {med['expiryDate']}")
if med.get("notes"):
lines.append(f"Instructions: {med['notes']}")
lines.append(f"\nScanned in {elapsed:.1f}s using {model_short}")
summary = "\n".join(lines)
api_json = json.dumps(
{"success": True, "medicine": med, "model_used": result.get("model_used"),
"scan_time_ms": int(elapsed * 1000)},
indent=2, ensure_ascii=False,
)
return summary, api_json
# ============================================================
# Gradio Interface
# ============================================================
EXAMPLES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "examples")
example_images = []
if os.path.isdir(EXAMPLES_DIR):
for fname in sorted(os.listdir(EXAMPLES_DIR)):
if fname.lower().endswith((".jpg", ".jpeg", ".png")):
example_images.append([os.path.join(EXAMPLES_DIR, fname), ""])
demo = gr.Interface(
fn=scan_image_ui,
inputs=[
gr.Image(label="Medicine Image", type="filepath"),
gr.Textbox(
label="HuggingFace Token (optional β€” auto-provided when used from MedOS)",
placeholder="hf_... β€” only needed for standalone use",
type="password",
),
],
outputs=[
gr.Textbox(label="Extracted Information", lines=10),
gr.Textbox(label="API Response (JSON)", lines=12),
],
examples=example_images if example_images else None,
cache_examples=False,
title="MedOS Medicine Scanner",
description=(
"Scan medicine packages, labels, or prescriptions with your camera. "
"The AI extracts drug name, dosage, form, expiry date, and instructions automatically.\n\n"
"**From MedOS:** Token is provided automatically β€” just scan.\n"
"**Standalone:** Enter a free [HuggingFace token](https://huggingface.co/settings/tokens/new?ownUserPermissions=inference.serverless.write&tokenType=fineGrained) "
"with *'Make calls to Inference Providers'* permission."
),
article=(
"**Privacy:** Images are processed via HuggingFace Inference API and are not stored.\n\n"
"**Disclaimer:** For informational purposes only. Always verify with a pharmacist."
),
flagging_mode="never",
)
if __name__ == "__main__":
demo.launch()