Download app/validation.py from ezzDevil/urban: direct link, hf CLI and curl.
- Browser
- Download file 2.97 kB
-
https://huggingface.co/spaces/ezzDevil/urban/resolve/main/app/validation.py
- Command line
-
hf download hf://spaces/ezzDevil/urban/app/validation.py
-
curl -L -o validation.py https://huggingface.co/spaces/ezzDevil/urban/resolve/main/app/validation.py
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)) | |