Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,12 +3,13 @@ import torch
|
|
| 3 |
from transformers import AutoModelForVision2Seq, AutoProcessor
|
| 4 |
from PIL import Image
|
| 5 |
import numpy as np
|
| 6 |
-
from fastapi import FastAPI, UploadFile, File
|
| 7 |
from typing import List
|
| 8 |
import io
|
| 9 |
import gradio as gr
|
|
|
|
| 10 |
|
| 11 |
-
# Initialize FastAPI app
|
| 12 |
app = FastAPI()
|
| 13 |
|
| 14 |
# Load SmolVLM-Instruct model and processor
|
|
@@ -19,35 +20,54 @@ model = AutoModelForVision2Seq.from_pretrained(model_id, token=os.environ.get("H
|
|
| 19 |
# Harmful objects list for detection
|
| 20 |
harmful_objects = ["knife", "gun", "weapon", "blood", "syringe", "bomb", "blade"]
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
@app.post("/predict")
|
| 23 |
async def predict(files: List[UploadFile] = File(...)):
|
|
|
|
|
|
|
|
|
|
| 24 |
results = []
|
| 25 |
image_embeddings = []
|
|
|
|
| 26 |
|
| 27 |
-
for file in files:
|
| 28 |
try:
|
| 29 |
-
#
|
| 30 |
image_data = await file.read()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
image = Image.open(io.BytesIO(image_data)).convert("RGB")
|
|
|
|
| 32 |
|
| 33 |
# Generate description
|
| 34 |
-
prompt_desc = "<image> Provide a detailed description of the image, including objects, colors, and context."
|
| 35 |
inputs = processor(text=[prompt_desc], images=[image], return_tensors="pt", padding=True)
|
| 36 |
outputs = model.generate(**inputs, max_length=512)
|
| 37 |
description = processor.decode(outputs[0], skip_special_tokens=True).replace(prompt_desc, "").strip()
|
| 38 |
|
| 39 |
# Extract signs/number plates (OCR)
|
| 40 |
-
prompt_ocr = "<image> Extract all visible text in the image, such as signs
|
| 41 |
inputs_ocr = processor(text=[prompt_ocr], images=[image], return_tensors="pt", padding=True)
|
| 42 |
ocr_outputs = model.generate(**inputs_ocr, max_length=512)
|
| 43 |
signs_text = processor.decode(ocr_outputs[0], skip_special_tokens=True).replace(prompt_ocr, "").strip()
|
| 44 |
|
| 45 |
-
# Detect harmful objects/blood
|
| 46 |
-
prompt_detect = "<image> Identify any harmful objects (e.g., knife, gun, blood, syringe, bomb, blade) in the image. List them explicitly."
|
| 47 |
inputs_detect = processor(text=[prompt_detect], images=[image], return_tensors="pt", padding=True)
|
| 48 |
detect_outputs = model.generate(**inputs_detect, max_length=512)
|
| 49 |
detected_objects = processor.decode(detect_outputs[0], skip_special_tokens=True).lower()
|
| 50 |
-
harmful_detected =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
# Get image embedding for similarity
|
| 53 |
inputs_emb = processor(images=[image], return_tensors="pt", padding=True)
|
|
@@ -56,15 +76,19 @@ async def predict(files: List[UploadFile] = File(...)):
|
|
| 56 |
image_embeddings.append(emb)
|
| 57 |
|
| 58 |
results.append({
|
|
|
|
|
|
|
| 59 |
"description": description if description else "No description generated.",
|
| 60 |
"signs": signs_text if signs_text else "None detected",
|
| 61 |
-
"
|
| 62 |
})
|
| 63 |
except Exception as e:
|
| 64 |
results.append({
|
|
|
|
|
|
|
| 65 |
"description": "Error processing image.",
|
| 66 |
"signs": "Error",
|
| 67 |
-
"
|
| 68 |
"error": str(e)
|
| 69 |
})
|
| 70 |
|
|
@@ -75,40 +99,50 @@ async def predict(files: List[UploadFile] = File(...)):
|
|
| 75 |
sim = np.dot(base_embedding, image_embeddings[i].T) / (
|
| 76 |
np.linalg.norm(base_embedding) * np.linalg.norm(image_embeddings[i])
|
| 77 |
)
|
| 78 |
-
results[i]["
|
| 79 |
|
| 80 |
-
return results
|
| 81 |
|
| 82 |
# Gradio interface for Hugging Face Spaces
|
| 83 |
def gradio_predict(*images):
|
|
|
|
|
|
|
|
|
|
| 84 |
results = []
|
| 85 |
image_embeddings = []
|
|
|
|
| 86 |
|
| 87 |
-
for image in images:
|
| 88 |
if image is None:
|
| 89 |
continue
|
| 90 |
try:
|
| 91 |
-
# Convert Gradio image input to PIL
|
| 92 |
image = Image.fromarray(image).convert("RGB")
|
|
|
|
| 93 |
|
| 94 |
# Generate description
|
| 95 |
-
prompt_desc = "<image> Provide a detailed description of the image, including objects, colors, and context."
|
| 96 |
inputs = processor(text=[prompt_desc], images=[image], return_tensors="pt", padding=True)
|
| 97 |
outputs = model.generate(**inputs, max_length=512)
|
| 98 |
description = processor.decode(outputs[0], skip_special_tokens=True).replace(prompt_desc, "").strip()
|
| 99 |
|
| 100 |
# Extract signs/number plates (OCR)
|
| 101 |
-
prompt_ocr = "<image> Extract all visible text in the image, such as signs
|
| 102 |
-
inputs_ocr = processor
|
| 103 |
ocr_outputs = model.generate(**inputs_ocr, max_length=512)
|
| 104 |
signs_text = processor.decode(ocr_outputs[0], skip_special_tokens=True).replace(prompt_ocr, "").strip()
|
| 105 |
|
| 106 |
-
# Detect harmful objects/blood
|
| 107 |
-
prompt_detect = "<image> Identify any harmful objects (e.g., knife, gun, blood, syringe, bomb, blade) in the image. List them explicitly."
|
| 108 |
inputs_detect = processor(text=[prompt_detect], images=[image], return_tensors="pt", padding=True)
|
| 109 |
detect_outputs = model.generate(**inputs_detect, max_length=512)
|
| 110 |
detected_objects = processor.decode(detect_outputs[0], skip_special_tokens=True).lower()
|
| 111 |
-
harmful_detected =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
# Get image embedding for similarity
|
| 114 |
inputs_emb = processor(images=[image], return_tensors="pt", padding=True)
|
|
@@ -117,15 +151,19 @@ def gradio_predict(*images):
|
|
| 117 |
image_embeddings.append(emb)
|
| 118 |
|
| 119 |
results.append({
|
|
|
|
|
|
|
| 120 |
"description": description if description else "No description generated.",
|
| 121 |
"signs": signs_text if signs_text else "None detected",
|
| 122 |
-
"
|
| 123 |
})
|
| 124 |
except Exception as e:
|
| 125 |
results.append({
|
|
|
|
|
|
|
| 126 |
"description": "Error processing image.",
|
| 127 |
"signs": "Error",
|
| 128 |
-
"
|
| 129 |
"error": str(e)
|
| 130 |
})
|
| 131 |
|
|
@@ -136,30 +174,27 @@ def gradio_predict(*images):
|
|
| 136 |
sim = np.dot(base_embedding, image_embeddings[i].T) / (
|
| 137 |
np.linalg.norm(base_embedding) * np.linalg.norm(image_embeddings[i])
|
| 138 |
)
|
| 139 |
-
results[i]["
|
| 140 |
|
| 141 |
-
# Format output for Gradio
|
| 142 |
-
output = ""
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
output += f"- Similarity to Image 1: {result['similarity']:.2f}\n"
|
| 150 |
if "error" in result:
|
| 151 |
-
output += f"
|
| 152 |
-
output += "\n"
|
| 153 |
-
|
| 154 |
return output
|
| 155 |
|
| 156 |
# Gradio interface
|
| 157 |
iface = gr.Interface(
|
| 158 |
fn=gradio_predict,
|
| 159 |
-
inputs=[gr.Image(label=f"Upload Image {i+1}") for i in range(3)], # Allow up to 3 images
|
| 160 |
-
outputs=gr.
|
| 161 |
-
title="VisionSage: Image Analysis
|
| 162 |
-
description="Upload up to 3 images to get detailed descriptions, extract signs/number plates, detect harmful objects/blood, and compute similarity to the first image."
|
| 163 |
)
|
| 164 |
|
| 165 |
if __name__ == "__main__":
|
|
|
|
| 3 |
from transformers import AutoModelForVision2Seq, AutoProcessor
|
| 4 |
from PIL import Image
|
| 5 |
import numpy as np
|
| 6 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException
|
| 7 |
from typing import List
|
| 8 |
import io
|
| 9 |
import gradio as gr
|
| 10 |
+
from datetime import datetime
|
| 11 |
|
| 12 |
+
# Initialize FastAPI app with increased upload limit (10MB)
|
| 13 |
app = FastAPI()
|
| 14 |
|
| 15 |
# Load SmolVLM-Instruct model and processor
|
|
|
|
| 20 |
# Harmful objects list for detection
|
| 21 |
harmful_objects = ["knife", "gun", "weapon", "blood", "syringe", "bomb", "blade"]
|
| 22 |
|
| 23 |
+
# Resize image to max 1024x1024 while preserving aspect ratio
|
| 24 |
+
def resize_image(image: Image.Image, max_size: int = 1024) -> Image.Image:
|
| 25 |
+
image.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
|
| 26 |
+
return image
|
| 27 |
+
|
| 28 |
@app.post("/predict")
|
| 29 |
async def predict(files: List[UploadFile] = File(...)):
|
| 30 |
+
if len(files) > 3:
|
| 31 |
+
raise HTTPException(status_code=400, detail="Maximum 3 images allowed.")
|
| 32 |
+
|
| 33 |
results = []
|
| 34 |
image_embeddings = []
|
| 35 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 36 |
|
| 37 |
+
for idx, file in enumerate(files, 1):
|
| 38 |
try:
|
| 39 |
+
# Check file size (limit to 10MB)
|
| 40 |
image_data = await file.read()
|
| 41 |
+
if len(image_data) > 10 * 1024 * 1024:
|
| 42 |
+
raise ValueError("Image file size exceeds 10MB limit.")
|
| 43 |
+
|
| 44 |
+
# Read and resize image
|
| 45 |
image = Image.open(io.BytesIO(image_data)).convert("RGB")
|
| 46 |
+
image = resize_image(image, max_size=1024)
|
| 47 |
|
| 48 |
# Generate description
|
| 49 |
+
prompt_desc = "<image> Provide a detailed description of the image, including objects, colors, people, and environmental context."
|
| 50 |
inputs = processor(text=[prompt_desc], images=[image], return_tensors="pt", padding=True)
|
| 51 |
outputs = model.generate(**inputs, max_length=512)
|
| 52 |
description = processor.decode(outputs[0], skip_special_tokens=True).replace(prompt_desc, "").strip()
|
| 53 |
|
| 54 |
# Extract signs/number plates (OCR)
|
| 55 |
+
prompt_ocr = "<image> Extract all visible text in the image, such as road signs, license plates, or billboards, with exact wording."
|
| 56 |
inputs_ocr = processor(text=[prompt_ocr], images=[image], return_tensors="pt", padding=True)
|
| 57 |
ocr_outputs = model.generate(**inputs_ocr, max_length=512)
|
| 58 |
signs_text = processor.decode(ocr_outputs[0], skip_special_tokens=True).replace(prompt_ocr, "").strip()
|
| 59 |
|
| 60 |
+
# Detect harmful objects/blood with confidence simulation
|
| 61 |
+
prompt_detect = "<image> Identify any harmful objects (e.g., knife, gun, blood, syringe, bomb, blade) in the image. List them explicitly and estimate confidence (0-100%) for each detection based on clarity."
|
| 62 |
inputs_detect = processor(text=[prompt_detect], images=[image], return_tensors="pt", padding=True)
|
| 63 |
detect_outputs = model.generate(**inputs_detect, max_length=512)
|
| 64 |
detected_objects = processor.decode(detect_outputs[0], skip_special_tokens=True).lower()
|
| 65 |
+
harmful_detected = []
|
| 66 |
+
for obj in harmful_objects:
|
| 67 |
+
if obj in detected_objects:
|
| 68 |
+
confidence = 90 if obj in detected_objects.split() else 60
|
| 69 |
+
harmful_detected.append({"object": obj, "confidence": confidence})
|
| 70 |
+
harmful_output = harmful_detected if harmful_detected else [{"object": "None", "confidence": 0}]
|
| 71 |
|
| 72 |
# Get image embedding for similarity
|
| 73 |
inputs_emb = processor(images=[image], return_tensors="pt", padding=True)
|
|
|
|
| 76 |
image_embeddings.append(emb)
|
| 77 |
|
| 78 |
results.append({
|
| 79 |
+
"image_id": f"Image_{idx}",
|
| 80 |
+
"timestamp": timestamp,
|
| 81 |
"description": description if description else "No description generated.",
|
| 82 |
"signs": signs_text if signs_text else "None detected",
|
| 83 |
+
"harmful_objects": harmful_output
|
| 84 |
})
|
| 85 |
except Exception as e:
|
| 86 |
results.append({
|
| 87 |
+
"image_id": f"Image_{idx}",
|
| 88 |
+
"timestamp": timestamp,
|
| 89 |
"description": "Error processing image.",
|
| 90 |
"signs": "Error",
|
| 91 |
+
"harmful_objects": [{"object": "Error", "confidence": 0}],
|
| 92 |
"error": str(e)
|
| 93 |
})
|
| 94 |
|
|
|
|
| 99 |
sim = np.dot(base_embedding, image_embeddings[i].T) / (
|
| 100 |
np.linalg.norm(base_embedding) * np.linalg.norm(image_embeddings[i])
|
| 101 |
)
|
| 102 |
+
results[i]["similarity_to_image_1"] = float(sim[0][0])
|
| 103 |
|
| 104 |
+
return {"results": results, "analysis_timestamp": timestamp}
|
| 105 |
|
| 106 |
# Gradio interface for Hugging Face Spaces
|
| 107 |
def gradio_predict(*images):
|
| 108 |
+
if len([img for img in images if img is not None]) > 3:
|
| 109 |
+
return "Error: Maximum 3 images allowed."
|
| 110 |
+
|
| 111 |
results = []
|
| 112 |
image_embeddings = []
|
| 113 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 114 |
|
| 115 |
+
for idx, image in enumerate(images, 1):
|
| 116 |
if image is None:
|
| 117 |
continue
|
| 118 |
try:
|
| 119 |
+
# Convert Gradio image input to PIL and resize
|
| 120 |
image = Image.fromarray(image).convert("RGB")
|
| 121 |
+
image = resize_image(image, max_size=1024)
|
| 122 |
|
| 123 |
# Generate description
|
| 124 |
+
prompt_desc = "<image> Provide a detailed description of the image, including objects, colors, people, and environmental context."
|
| 125 |
inputs = processor(text=[prompt_desc], images=[image], return_tensors="pt", padding=True)
|
| 126 |
outputs = model.generate(**inputs, max_length=512)
|
| 127 |
description = processor.decode(outputs[0], skip_special_tokens=True).replace(prompt_desc, "").strip()
|
| 128 |
|
| 129 |
# Extract signs/number plates (OCR)
|
| 130 |
+
prompt_ocr = "<image> Extract all visible text in the image, such as road signs, license plates, or billboards, with exact wording."
|
| 131 |
+
inputs_ocr = processor[text=[prompt_ocr], images=[image], return_tensors="pt", padding=True)
|
| 132 |
ocr_outputs = model.generate(**inputs_ocr, max_length=512)
|
| 133 |
signs_text = processor.decode(ocr_outputs[0], skip_special_tokens=True).replace(prompt_ocr, "").strip()
|
| 134 |
|
| 135 |
+
# Detect harmful objects/blood with confidence simulation
|
| 136 |
+
prompt_detect = "<image> Identify any harmful objects (e.g., knife, gun, blood, syringe, bomb, blade) in the image. List them explicitly and estimate confidence (0-100%) for each detection based on clarity."
|
| 137 |
inputs_detect = processor(text=[prompt_detect], images=[image], return_tensors="pt", padding=True)
|
| 138 |
detect_outputs = model.generate(**inputs_detect, max_length=512)
|
| 139 |
detected_objects = processor.decode(detect_outputs[0], skip_special_tokens=True).lower()
|
| 140 |
+
harmful_detected = []
|
| 141 |
+
for obj in harmful_objects:
|
| 142 |
+
if obj in detected_objects:
|
| 143 |
+
confidence = 90 if obj in detected_objects.split() else 60
|
| 144 |
+
harmful_detected.append({"object": obj, "confidence": confidence})
|
| 145 |
+
harmful_output = harmful_detected if harmful_detected else [{"object": "None", "confidence": 0}]
|
| 146 |
|
| 147 |
# Get image embedding for similarity
|
| 148 |
inputs_emb = processor(images=[image], return_tensors="pt", padding=True)
|
|
|
|
| 151 |
image_embeddings.append(emb)
|
| 152 |
|
| 153 |
results.append({
|
| 154 |
+
"image_id": f"Image_{idx}",
|
| 155 |
+
"timestamp": timestamp,
|
| 156 |
"description": description if description else "No description generated.",
|
| 157 |
"signs": signs_text if signs_text else "None detected",
|
| 158 |
+
"harmful_objects": harmful_output
|
| 159 |
})
|
| 160 |
except Exception as e:
|
| 161 |
results.append({
|
| 162 |
+
"image_id": f"Image_{idx}",
|
| 163 |
+
"timestamp": timestamp,
|
| 164 |
"description": "Error processing image.",
|
| 165 |
"signs": "Error",
|
| 166 |
+
"harmful_objects": [{"object": "Error", "confidence": 0}],
|
| 167 |
"error": str(e)
|
| 168 |
})
|
| 169 |
|
|
|
|
| 174 |
sim = np.dot(base_embedding, image_embeddings[i].T) / (
|
| 175 |
np.linalg.norm(base_embedding) * np.linalg.norm(image_embeddings[i])
|
| 176 |
)
|
| 177 |
+
results[i]["similarity_to_image_1"] = float(sim[0][0])
|
| 178 |
|
| 179 |
+
# Format output for Gradio as tabulated markdown
|
| 180 |
+
output = f"**Analysis Timestamp**: {timestamp}\n\n"
|
| 181 |
+
output += "| Image ID | Description | Signs/Number Plates | Harmful Objects | Similarity to Image 1 |\n"
|
| 182 |
+
output += "|----------|-------------|---------------------|-----------------|-----------------------|\n"
|
| 183 |
+
for result in results:
|
| 184 |
+
harmful_str = ", ".join([f"{obj['object']} ({obj['confidence']}%)" for obj in result['harmful_objects']])
|
| 185 |
+
similarity = f"{result['similarity_to_image_1']:.2f}" if 'similarity_to_image_1' in result else "N/A"
|
| 186 |
+
output += f"| {result['image_id']} ({result['timestamp']}) | {result['description']} | {result['signs']} | {harmful_str} | {similarity} |\n"
|
|
|
|
| 187 |
if "error" in result:
|
| 188 |
+
output += f"| **Error** | {result['error']} | - | - | - |\n"
|
|
|
|
|
|
|
| 189 |
return output
|
| 190 |
|
| 191 |
# Gradio interface
|
| 192 |
iface = gr.Interface(
|
| 193 |
fn=gradio_predict,
|
| 194 |
+
inputs=[gr.Image(label=f"Upload Image {i+1} (up to 10MB)") for i in range(3)], # Allow up to 3 images
|
| 195 |
+
outputs=gr.Markdown(label="Investigation Results"),
|
| 196 |
+
title="VisionSage: Image Analysis for Investigation",
|
| 197 |
+
description="Upload up to 3 images (up to 10MB each, any resolution) to get detailed descriptions, extract signs/number plates, detect harmful objects/blood with confidence scores, and compute similarity to the first image. Results are formatted for investigative analysis."
|
| 198 |
)
|
| 199 |
|
| 200 |
if __name__ == "__main__":
|