File size: 2,971 Bytes
cbff690
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
"""
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))