Spaces:
Sleeping
Sleeping
File size: 5,872 Bytes
35fefbe 6f23c17 35fefbe 6f23c17 35fefbe 6ec7266 35fefbe 6ec7266 35fefbe 6ec7266 35fefbe 6ec7266 35fefbe 6ec7266 35fefbe 6ec7266 35fefbe 6ec7266 35fefbe 6f23c17 23e7f36 6f23c17 35fefbe 6f23c17 35fefbe 6f23c17 35fefbe 6f23c17 23e7f36 6f23c17 | 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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | """Florence-2 scene tagger (ZeroGPU): proposes class names from an image.
The first implementation used only the Florence ``<OD>`` task, which is too
sparse for street-level panoptic work because object detection misses broad
surface/stuff classes. This endpoint now combines object detection, dense
region captions, and detailed caption text into one open-vocabulary concept
list for SAM3.
"""
import os
from collections import Counter
import re
import gradio as gr
import spaces
import torch
from transformers import AutoProcessor, Florence2ForConditionalGeneration
MODEL_ID = "florence-community/Florence-2-large"
# Built at import on CPU; moved to CUDA inside the @spaces.GPU function.
processor = AutoProcessor.from_pretrained(MODEL_ID)
model = Florence2ForConditionalGeneration.from_pretrained(MODEL_ID)
model.eval()
TASKS = ("<OD>", "<DENSE_REGION_CAPTION>", "<MORE_DETAILED_CAPTION>")
DROP_WORDS = {
"a", "an", "the", "this", "that", "these", "those", "there", "here",
"image", "photo", "picture", "view", "scene", "background", "foreground",
"left", "right", "top", "bottom", "front", "back", "side", "area", "part",
"visible", "large", "small", "several", "multiple", "many", "some",
"is", "are", "was", "were", "be", "being", "been", "appears", "appear",
"of", "in", "it", "its", "to", "for", "by", "as", "at", "from", "into",
"parked", "parking", "surrounded", "including", "taken", "shining",
"brightly", "different", "models", "colors", "color", "angle", "high",
"panoramic", "modern", "curved", "few", "blue", "red", "white",
}
KEEP_PHRASES = {
"parking lot",
}
DROP_LABELS = {
"roof", "floor", "floors", "balcony", "balconies", "sun", "cloud", "clouds",
"sky", "brightly", "angle", "high angle",
}
CAPTION_SPLIT = re.compile(
r"[,.;:]|\\bwith\\b|\\band\\b|\\bnext to\\b|\\bin front of\\b|\\bbehind\\b|\\bon\\b|\\balong\\b|\\bnear\\b|\\bsurrounded by\\b",
flags=re.IGNORECASE,
)
def _clean_label(value: str) -> str:
text = str(value).strip().lower()
text = re.sub(r"[_/\\-]+", " ", text)
text = re.sub(r"[^a-z0-9\\s]+", " ", text)
for phrase in KEEP_PHRASES:
if phrase in text:
return phrase
words = [w for w in text.split() if w and w not in DROP_WORDS]
if not words:
return ""
if len(words) == 1:
word = words[0]
if len(word) > 3 and word.endswith("ies"):
word = word[:-3] + "y"
elif len(word) > 3 and word.endswith("s") and not word.endswith("ss"):
word = word[:-1]
words = [word]
if len(words) > 5:
words = words[-5:]
label = " ".join(words)
return "" if label in DROP_LABELS else label
def _labels_from_caption(text: str) -> list[str]:
labels: list[str] = []
for chunk in CAPTION_SPLIT.split(str(text)):
clean = _clean_label(chunk)
if not clean:
continue
words = clean.split()
candidates = [clean]
if len(words) >= 3:
candidates.append(" ".join(words[-2:]))
candidates.append(words[-1])
for candidate in candidates:
candidate = _clean_label(candidate)
if candidate and len(candidate) > 2 and candidate not in labels:
labels.append(candidate)
break
return labels
def _extract_labels(parsed) -> list[str]:
labels: list[str] = []
def walk(obj, key_hint: str = ""):
if isinstance(obj, dict):
for key, value in obj.items():
k = str(key).lower()
if k in {"label", "labels", "caption", "captions", "text", "description", "descriptions"}:
walk(value, k)
else:
walk(value, key_hint)
elif isinstance(obj, (list, tuple)):
for item in obj:
walk(item, key_hint)
elif isinstance(obj, str):
if key_hint in {"label", "labels"}:
clean = _clean_label(obj)
if clean:
labels.append(clean)
else:
labels.extend(_labels_from_caption(obj))
walk(parsed)
return labels
def _run_florence_task(image, task: str, num_beams: int) -> dict:
device = "cuda"
model.to(device)
inputs = processor(text=task, images=image, return_tensors="pt").to(device)
with torch.no_grad():
gen = model.generate(**inputs, max_new_tokens=1536, num_beams=int(num_beams))
text = processor.batch_decode(gen, skip_special_tokens=False)[0]
parsed = processor.post_process_generation(text, task=task, image_size=image.size)
return {"task": task, "text": text, "parsed": parsed, "labels": _extract_labels(parsed)}
@spaces.GPU(duration=120)
def api_autotag(image, max_tags, num_beams=3):
if image is None:
return {"error": "no image provided"}
image = image.convert("RGB")
task_results = [_run_florence_task(image, task, int(num_beams)) for task in TASKS]
labels = []
for result in task_results:
labels.extend(result["labels"])
counts = Counter(label for label in labels if label)
tags = [{"name": n, "count": c} for n, c in counts.most_common(int(max_tags))]
return {"model": MODEL_ID, "tasks": task_results, "tags": tags, "labels": [t["name"] for t in tags]}
with gr.Blocks(title="SAM3 AutoTag") as demo:
gr.Markdown("# Florence-2 AutoTag\nUpload an image; returns multi-task scene class names for SAM3.")
with gr.Row():
inp = gr.Image(type="pil", label="Image")
out = gr.JSON(label="Tags")
mt = gr.Slider(1, 50, value=20, step=1, label="Max tags")
nb = gr.Slider(1, 8, value=3, step=1, label="Beams (higher = more precise)")
gr.Button("Tag").click(api_autotag, [inp, mt, nb], out, api_name="api_autotag")
if __name__ == "__main__":
demo.queue().launch(show_error=True)
|