Spaces:
Sleeping
Sleeping
| """ | |
| FastAPI server for Hugging Face Space. | |
| Deploy this to https://huggingface.co/spaces/ditoow/trashscan8n | |
| Usage: | |
| pip install fastapi uvicorn onnxruntime pillow numpy | |
| uvicorn hf_space_server:app --host 0.0.0.0 --port 7860 | |
| """ | |
| import io | |
| import numpy as np | |
| from PIL import Image | |
| from fastapi import FastAPI, Request | |
| from fastapi.responses import JSONResponse | |
| import onnxruntime as ort | |
| app = FastAPI() | |
| MODEL_PATH = "best.onnx" | |
| session = None | |
| input_name = None | |
| output_name = None | |
| CLASSES = ["paper", "plastic", "metal", "organic", "other"] | |
| async def load_model(): | |
| global session, input_name, output_name | |
| session = ort.InferenceSession(MODEL_PATH) | |
| input_name = session.get_inputs()[0].name | |
| output_name = session.get_outputs()[0].name | |
| def preprocess(image: Image.Image) -> np.ndarray: | |
| orig_w, orig_h = image.size | |
| scale = 640 / max(orig_w, orig_h) | |
| new_w = int(orig_w * scale) | |
| new_h = int(orig_h * scale) | |
| resized = image.resize((new_w, new_h), Image.LANCZOS) | |
| canvas = Image.new("RGB", (640, 640), (0, 0, 0)) | |
| dx = (640 - new_w) // 2 | |
| dy = (640 - new_h) // 2 | |
| canvas.paste(resized, (dx, dy)) | |
| img_array = np.array(canvas, dtype=np.float32) / 255.0 | |
| img_array = img_array.transpose(2, 0, 1) | |
| img_array = np.expand_dims(img_array, axis=0) | |
| return img_array, scale, dx, dy, orig_w, orig_h | |
| def postprocess(output: np.ndarray, scale, dx, dy, orig_w, orig_h, conf_thresh=0.1, iou_thresh=0.5): | |
| output = output.squeeze() | |
| num_classes = len(CLASSES) | |
| num_boxes = output.shape[1] | |
| boxes = [] | |
| for i in range(num_boxes): | |
| cx, cy, w, h = output[0, i], output[1, i], output[2, i], output[3, i] | |
| scores = output[4:4 + num_classes, i] | |
| max_conf = float(scores.max()) | |
| if max_conf < conf_thresh: | |
| continue | |
| class_id = int(scores.argmax()) | |
| x1 = (cx - w / 2 - dx) / (scale * orig_w) | |
| y1 = (cy - h / 2 - dy) / (scale * orig_h) | |
| x2 = (cx + w / 2 - dx) / (scale * orig_w) | |
| y2 = (cy + h / 2 - dy) / (scale * orig_h) | |
| x1 = max(0, min(1, x1)) | |
| y1 = max(0, min(1, y1)) | |
| x2 = max(0, min(1, x2)) | |
| y2 = max(0, min(1, y2)) | |
| boxes.append({ | |
| "label": CLASSES[class_id], | |
| "score": float(max_conf), | |
| "xmin": float(x1), | |
| "ymin": float(y1), | |
| "xmax": float(x2), | |
| "ymax": float(y2), | |
| }) | |
| # NMS | |
| boxes.sort(key=lambda b: b["score"], reverse=True) | |
| kept = [] | |
| for box in boxes: | |
| if not any(iou(box, k) > iou_thresh for k in kept): | |
| kept.append(box) | |
| return kept | |
| def iou(a, b): | |
| x1 = max(a["xmin"], b["xmin"]) | |
| y1 = max(a["ymin"], b["ymin"]) | |
| x2 = min(a["xmax"], b["xmax"]) | |
| y2 = min(a["ymax"], b["ymax"]) | |
| inter = max(0, x2 - x1) * max(0, y2 - y1) | |
| area_a = (a["xmax"] - a["xmin"]) * (a["ymax"] - a["ymin"]) | |
| area_b = (b["xmax"] - b["xmin"]) * (b["ymax"] - b["ymin"]) | |
| union = area_a + area_b - inter | |
| return inter / union if union > 0 else 0 | |
| async def detect(request: Request): | |
| import traceback | |
| try: | |
| body = await request.body() | |
| if len(body) < 100: | |
| return JSONResponse(content={"error": f"Image too small: {len(body)} bytes"}, status_code=400) | |
| try: | |
| image = Image.open(io.BytesIO(body)).convert("RGB") | |
| except Exception as e: | |
| return JSONResponse(content={"error": f"Invalid image: {e}"}, status_code=400) | |
| orig_w, orig_h = image.size | |
| tensor, scale, dx, dy, _, _ = preprocess(image) | |
| output = session.run([output_name], {input_name: tensor})[0] | |
| results = postprocess(output, scale, dx, dy, orig_w, orig_h) | |
| return JSONResponse(content=results) | |
| except Exception as e: | |
| tb = traceback.format_exc() | |
| return JSONResponse(content={"error": str(e), "trace": tb}, status_code=500) | |
| async def health(): | |
| return {"status": "ok"} | |