gazeTracker / main.py
JSWOOK's picture
Deploy L2CS gaze WebSocket service
9c0eec0
Raw History Blame Contribute Delete
2.07 kB
import asyncio
import json
from pathlib import Path
import cv2
import numpy as np
import torch
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
app = FastAPI(title="Reading Trace L2CS")
pipeline = None
def get_pipeline():
global pipeline
if pipeline is None:
from l2cs import Pipeline
pipeline = Pipeline(
weights=Path("models/L2CSNet_gaze360.pkl"),
arch="ResNet50",
device=torch.device("cuda" if torch.cuda.is_available() else "cpu"),
)
return pipeline
@app.get("/health")
def health():
get_pipeline()
return {"status": "ok", "device": "cuda" if torch.cuda.is_available() else "cpu"}
def infer(data: bytes):
frame = cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR)
if frame is None:
return {"error": "invalid frame"}
height, width = frame.shape[:2]
try:
result = get_pipeline().step(frame)
except ValueError:
return {"faces": 0}
yaw = np.asarray(result.yaw).reshape(-1)
pitch = np.asarray(result.pitch).reshape(-1)
if not yaw.size:
return {"faces": 0}
boxes = np.asarray(getattr(result, "bboxes", []))
index, fx, fy = 0, .5, .5
if boxes.ndim == 2 and boxes.shape[0] == yaw.size and boxes.shape[1] >= 4:
areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
index = int(np.argmax(areas))
x0, y0, x1, y1 = boxes[index, :4]
fx, fy = float((x0 + x1) / 2 / width), float((y0 + y1) / 2 / height)
return {"yaw": float(yaw[index]), "pitch": float(pitch[index]), "fx": fx, "fy": fy, "faces": int(yaw.size)}
@app.websocket("/ws")
async def websocket_gaze(socket: WebSocket):
await socket.accept()
try:
while True:
data = await socket.receive_bytes()
if len(data) > 2_000_000:
await socket.close(code=1009)
return
result = await asyncio.to_thread(infer, data)
await socket.send_text(json.dumps(result))
except WebSocketDisconnect:
pass