SAM3-Panoptic / app.py
Sempy32's picture
Upload folder using huggingface_hub
1c31152 verified
Raw
History Blame Contribute Delete
3.1 kB
"""SAM 3 panoptic concept-segmentation API (ZeroGPU). Self-contained."""
import base64
import io
import os
import gradio as gr
import numpy as np
import spaces
import torch
from PIL import Image
from transformers import Sam3Model, Sam3Processor
HF_TOKEN = os.environ.get("HF_TOKEN")
MODEL_ID = "facebook/sam3"
# Built at import on CPU; moved to CUDA inside the @spaces.GPU function.
processor = Sam3Processor.from_pretrained(MODEL_ID, token=HF_TOKEN)
model = Sam3Model.from_pretrained(MODEL_ID, token=HF_TOKEN)
model.eval()
def _encode_mask(mask_bool: np.ndarray) -> str:
arr = (mask_bool.astype(np.uint8)) * 255
buf = io.BytesIO()
Image.fromarray(arr, mode="L").save(buf, format="PNG")
return base64.b64encode(buf.getvalue()).decode("ascii")
@spaces.GPU(duration=120)
def api_panoptic(image, concepts, conf, mask_threshold=0.5):
if image is None:
return {"error": "no image provided"}
image = image.convert("RGB")
W, H = image.size
concept_list = [c.strip() for c in (concepts or "").split(",") if c.strip()]
device = "cuda"
model.to(device)
detections = []
for concept in concept_list:
inputs = processor(images=image, text=concept, return_tensors="pt").to(device)
with torch.no_grad():
outputs = model(**inputs)
target_sizes = (inputs["original_sizes"].tolist()
if "original_sizes" in inputs else [[H, W]])
res = processor.post_process_instance_segmentation(
outputs, threshold=float(conf), mask_threshold=float(mask_threshold),
target_sizes=target_sizes)[0]
# NOTE (verify on live Space): expected keys masks/scores/boxes.
masks, scores = res["masks"], res["scores"]
boxes = res.get("boxes")
for i in range(len(scores)):
m = masks[i]
m = m.cpu().numpy() if hasattr(m, "cpu") else np.asarray(m)
mb = m > 0.5 if m.dtype != bool else m
box = (boxes[i].cpu().numpy().tolist()
if boxes is not None else [0, 0, 0, 0])
detections.append({
"label": concept, "score": float(scores[i]),
"box": box, "mask_png_b64": _encode_mask(mb.astype(bool)),
})
return {"version": "3", "model": MODEL_ID, "width": W, "height": H,
"detections": detections}
with gr.Blocks(title="SAM3 Panoptic") as demo:
gr.Markdown("# SAM 3 Panoptic API\nUpload an image, enter comma-separated concepts.")
with gr.Row():
inp = gr.Image(type="pil", label="Image")
out = gr.JSON(label="Detections")
txt = gr.Textbox(label="Concepts (comma-separated)",
value="person, car, road, sky, building, tree")
conf = gr.Slider(0.0, 1.0, value=0.4, step=0.05, label="Confidence (lower = more detail)")
mthr = gr.Slider(0.05, 0.95, value=0.5, step=0.05, label="Mask threshold")
gr.Button("Segment").click(api_panoptic, [inp, txt, conf, mthr], out,
api_name="api_panoptic")
if __name__ == "__main__":
demo.queue().launch()