urban / app /validation.py
ezzDevil's picture
Deploy UrbanFix FastAPI vision service to this Space
cbff690
Raw History Blame Contribute Delete
2.97 kB
"""
Image validation — Stage 0 of the pipeline.
Rejects selfies, screenshots, indoor photos, memes, blurry images, etc.
Uses SigLIP zero-shot matching against valid vs invalid prompts.
"""
import torch
import torch.nn.functional as F
from PIL import Image
from .config import settings
from .models import model_manager
from .schemas import ValidationResult
VALID_PROMPTS = [
"a photo of a road street sidewalk or outdoor infrastructure",
"a photo of garbage trash waste or litter on the ground outside",
"a photo of a street light lamp pole or outdoor electrical fixture",
"a photo of water flooding or waterlogged road outdoors",
"a photo of a park playground bench or outdoor public space",
]
INVALID_PROMPTS = [
"a selfie portrait photo of a person face close up",
"a screenshot of a phone computer screen or app interface",
"a photo taken indoors inside a room house or building",
"a meme funny image text overlay or internet joke picture",
"a blurry out of focus dark or completely unrecognizable image",
"a photo of food plate meal or restaurant",
"a document paper receipt or text page",
]
_REJECTION_REASONS = {
0: "Selfie / person portrait detected",
1: "Screenshot detected",
2: "Indoor photo detected",
3: "Meme / joke image detected",
4: "Blurry / unrecognizable image",
5: "Food photo detected",
6: "Document / text page detected",
}
def validate_image(pil_img: Image.Image) -> ValidationResult:
if model_manager.siglip_model is None:
return ValidationResult(is_valid=True, confidence=0)
w, h = pil_img.size
if w < settings.min_image_dimension or h < settings.min_image_dimension:
return ValidationResult(
is_valid=False,
confidence=1.0,
rejection_reason=f"Image too small ({w}x{h}). Minimum {settings.min_image_dimension}x{settings.min_image_dimension} px.",
)
all_prompts = VALID_PROMPTS + INVALID_PROMPTS
n_valid = len(VALID_PROMPTS)
inputs = model_manager.siglip_processor(
text=all_prompts,
images=pil_img,
return_tensors="pt",
padding="max_length",
truncation=True,
).to(model_manager.device)
with torch.no_grad():
outputs = model_manager.siglip_model(**inputs)
logits = outputs.logits_per_image
scores = F.softmax(logits, dim=-1)[0].cpu().tolist()
valid_score = sum(scores[:n_valid])
invalid_score = sum(scores[n_valid:])
if invalid_score > settings.validation_threshold:
best_idx = max(range(n_valid, len(scores)), key=lambda i: scores[i])
reason = _REJECTION_REASONS.get(
best_idx - n_valid, "Not a civic issue photo"
)
return ValidationResult(
is_valid=False,
confidence=round(invalid_score, 4),
rejection_reason=reason,
)
return ValidationResult(is_valid=True, confidence=round(valid_score, 4))