Spaces:
Sleeping
Sleeping
File size: 5,259 Bytes
05d4b75 bb06dcc 05d4b75 bb06dcc 05d4b75 bb06dcc 05d4b75 b2d0be0 05d4b75 bb06dcc b2d0be0 bb06dcc 05d4b75 b2d0be0 05d4b75 b2d0be0 05d4b75 b2d0be0 05d4b75 b2d0be0 bb06dcc b2d0be0 bb06dcc b2d0be0 05d4b75 bb06dcc b2d0be0 bb06dcc 05d4b75 b2d0be0 05d4b75 b2d0be0 bb06dcc b2d0be0 05d4b75 b2d0be0 bb06dcc 05d4b75 b2d0be0 bb06dcc 05d4b75 bb06dcc b2d0be0 bb06dcc b2d0be0 bb06dcc b2d0be0 05d4b75 bb06dcc 05d4b75 b2d0be0 05d4b75 bb06dcc 05d4b75 b2d0be0 05d4b75 bb06dcc 05d4b75 bb06dcc 05d4b75 bb06dcc 05d4b75 ed90657 | 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 | """
app.py — Hugging Face Space to test the SpotPredator PicoDet model in the browser.
Pulls the .tflite straight from the model repo, so testers install nothing — they
drag an image in and see boxes.
Model: JZVince/spotpredator-picodet (PicoDet, Apache-2.0)
PicoDet is exported WITHOUT in-graph NMS, so the TFLite model has TWO outputs:
boxes: (1, N, 4) already-decoded x1,y1,x2,y2 in input-pixel space (0..640)
scores: (1, num_classes, N) per-class confidence
Preprocess is /255 then ImageNet mean/std (RGB) — matches the training pipeline.
"""
import os
import numpy as np
from PIL import Image, ImageDraw
import gradio as gr
from huggingface_hub import hf_hub_download, list_repo_files
MODEL_REPO = "JZVince/spotpredator-picodet"
CLASS_NAMES = ["coyote", "fox", "raptor"] # must match training order / labels.txt
# PicoDet (PaddleDetection) NormalizeImage: /255 then ImageNet mean/std, RGB.
IMAGENET_MEAN = np.array([0.485, 0.456, 0.406], dtype=np.float32)
IMAGENET_STD = np.array([0.229, 0.224, 0.225], dtype=np.float32)
# Optional: set HF_TOKEN as a Space secret for higher rate limits / faster pulls.
HF_TOKEN = os.environ.get("HF_TOKEN")
# ---- fetch the .tflite from the model repo (auto-detect the filename) --------
tflite_name = next(f for f in list_repo_files(MODEL_REPO, token=HF_TOKEN)
if f.endswith(".tflite"))
MODEL_PATH = hf_hub_download(MODEL_REPO, tflite_name, token=HF_TOKEN)
# LiteRT is the successor to tflite-runtime; fall back through older runtimes.
try:
from ai_edge_litert.interpreter import Interpreter
except ImportError:
try:
from tflite_runtime.interpreter import Interpreter
except ImportError:
from tensorflow.lite import Interpreter
interp = Interpreter(model_path=MODEL_PATH)
interp.allocate_tensors()
INP = interp.get_input_details()[0]
OUTS = interp.get_output_details()
IN_W = int(INP["shape"][2])
IN_H = int(INP["shape"][1])
NC = len(CLASS_NAMES)
# PicoDet has two outputs; identify boxes (last dim == 4) vs scores by shape, so
# output order doesn't matter.
BOX_I = 0 if OUTS[0]["shape"][-1] == 4 else 1
SCORE_I = 1 - BOX_I
# ---- NMS (class-aware, applied by caller) -----------------------------------
def nms(boxes, scores, iou_thr=0.45):
x1, y1, x2, y2 = boxes.T
areas = (x2 - x1) * (y2 - y1)
order = scores.argsort()[::-1]
keep = []
while order.size:
i = order[0]
keep.append(i)
xx1 = np.maximum(x1[i], x1[order[1:]])
yy1 = np.maximum(y1[i], y1[order[1:]])
xx2 = np.minimum(x2[i], x2[order[1:]])
yy2 = np.minimum(y2[i], y2[order[1:]])
w = np.maximum(0, xx2 - xx1)
h = np.maximum(0, yy2 - yy1)
inter = w * h
iou = inter / (areas[i] + areas[order[1:]] - inter + 1e-9)
order = order[1:][iou <= iou_thr]
return keep
def detect(image, conf_thr=0.5, iou_thr=0.45):
if image is None:
return None, "Upload an image."
img = image.convert("RGB")
# Preprocess: plain resize to model input, /255, then ImageNet mean/std.
resized = img.resize((IN_W, IN_H), Image.BILINEAR)
x = np.asarray(resized, np.float32) / 255.0
x = ((x - IMAGENET_MEAN) / IMAGENET_STD).astype(np.float32)[None]
interp.set_tensor(INP["index"], x)
interp.invoke()
boxes = interp.get_tensor(OUTS[BOX_I]["index"])[0] # (N, 4) x1,y1,x2,y2 in 0..IN_W
scores = interp.get_tensor(OUTS[SCORE_I]["index"])[0].T # (N, num_classes)
cid = scores.argmax(1)
conf = scores.max(1)
m = conf >= conf_thr
boxes, conf, cid = boxes[m], conf[m], cid[m]
# Scale boxes from model-input space (IN_W x IN_H) back to the original image.
sx = img.width / IN_W
sy = img.height / IN_H
draw = ImageDraw.Draw(img)
colors = [(220, 70, 60), (240, 160, 40), (60, 130, 200)]
lines = []
if len(boxes):
xyxy = boxes.astype(np.float32).copy()
xyxy[:, [0, 2]] *= sx
xyxy[:, [1, 3]] *= sy
xyxy[:, [0, 2]] = xyxy[:, [0, 2]].clip(0, img.width)
xyxy[:, [1, 3]] = xyxy[:, [1, 3]].clip(0, img.height)
for c in np.unique(cid):
idx = np.where(cid == c)[0]
for k in nms(xyxy[idx], conf[idx], iou_thr):
b = xyxy[idx][k]
col = colors[int(c) % 3]
draw.rectangle(list(b), outline=col, width=3)
draw.text((b[0] + 3, max(0, b[1] - 12)),
f"{CLASS_NAMES[int(c)]} {conf[idx][k]:.2f}", fill=col)
lines.append(f"{CLASS_NAMES[int(c)]}: {conf[idx][k]:.2f}")
return img, ("\n".join(lines) if lines else "No predators detected.")
demo = gr.Interface(
fn=detect,
inputs=[gr.Image(type="pil", label="Field image"),
gr.Slider(0.05, 0.9, 0.5, label="Confidence threshold"),
gr.Slider(0.1, 0.9, 0.45, label="NMS IoU")],
outputs=[gr.Image(type="pil", label="Detections"),
gr.Textbox(label="Results")],
title="SpotPredator — PicoDet",
description="Off-grid farm predator detector (coyote / fox / raptor). "
"Runs the PicoDet TFLite model directly (Apache-2.0). Drag in a field image.",
)
if __name__ == "__main__":
demo.launch(ssr_mode=False, show_api=False)
|