CoolFace
Apppublic

Mr-Saab29/poseflow_backend

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
server.ts176 linesDownload Raw Back to src
1// src/server.ts2import express from 'express';3import cors from 'cors';4import { z } from 'zod';5import {6  classifyLandmarks,7  loadModel,8  initTf,9  detectLandmarksFromImage,10} from "./pose.js";11 12const app = express();13app.use(cors());14app.use(express.json({ limit: '10mb', strict: true }));15 16// ---------------- Pose catalog (5 poses) ----------------17const ASSETS_BASE =18  process.env.POSE_ASSETS_BASE ??19  "https://huggingface.co/spaces/Mr-Saab29/poseflow_backend/resolve/main/file/assets";20 21export const POSES = ["Warrior", "Cobra", "Tree", "Triangle", "Dog"] as const;22type PoseName = typeof POSES[number];23 24const REF_IMAGES: Record<PoseName, string> = {25  Cobra:    `${ASSETS_BASE}/cobra.jpg`,26  Dog:      `${ASSETS_BASE}/dog.jpg`,27  Triangle: `${ASSETS_BASE}/traingle.jpg`, // note: file is spelled "traingle.jpg"28  Tree:     `${ASSETS_BASE}/tree.jpg`,29  Warrior:  `${ASSETS_BASE}/warrior.jpg`,30};31 32// ---------------- Info / health ----------------33app.get('/', (_req, res) =>34  res.json({35    ok: true,36    service: 'poseflow-backend',37    routes: ['/health', '/warmup', '/poses', '/classify', '/pose/predict', '/pose/verify'],38  })39);40app.get('/health', (_req, res) => res.json({ ok: true }));41 42// List the available poses and their reference URLs (for your UI)43app.get('/poses', (_req, res) => {44  res.json({45    poses: POSES.map((p) => ({ name: p, referenceUrl: REF_IMAGES[p] })),46  });47});48 49// ---------------- Warmup ----------------50app.post("/warmup", async (_req, res) => {51  try {52    await initTf();      // init backend + load model53    return res.json({ ok: true, warmed: true });54  } catch (e: any) {55    console.error("warmup error:", e);56    return res.status(500).json({ ok: false, error: "warmup_failed", message: e?.message ?? String(e) });57  }58});59 60// ---------------- Schemas ----------------61const Pair = z.tuple([z.number(), z.number()]);62const Landmarks = z.array(Pair).length(17);63const Threshold = z.number().min(0).max(1).default(0.8);64 65const ClassifyBody = z.object({66  landmarks: Landmarks,67  threshold: Threshold.optional(),68});69 70const PredictBody = z.object({71  landmarks: Landmarks.optional(),72  imageUrl: z.string().url().optional(),73  imageB64: z.string().min(1).optional(),74  threshold: Threshold.optional(),75}).refine((b) => !!b.landmarks || !!b.imageUrl || !!b.imageB64, {76  message: 'Provide either `landmarks` OR (`imageUrl` | `imageB64`).',77  path: ['landmarks'],78});79 80// User chooses a pose and uploads an image; we verify that pose’s probability81const VerifyBody = z.object({82  expectedPose: z.enum(POSES),83  imageUrl: z.string().url().optional(),84  imageB64: z.string().min(1).optional(),85  threshold: Threshold.optional(),86}).refine((b) => !!b.imageUrl || !!b.imageB64, {87  message: 'Provide `imageUrl` or `imageB64`.',88  path: ['imageUrl'],89});90 91// ---------------- Routes ----------------92app.post('/classify', async (req, res) => {93  const parsed = ClassifyBody.safeParse(req.body);94  if (!parsed.success) return res.status(400).json({ error: 'bad_request', details: parsed.error.errors });95  try {96    const { landmarks, threshold } = parsed.data;97    const out = await classifyLandmarks(landmarks, threshold ?? 0.8);98    res.json(out);99  } catch (e: any) {100    console.error('classify error:', e);101    res.status(500).json({ error: 'inference_failed', message: e?.message ?? String(e) });102  }103});104 105app.get('/pose/predict', (_req, res) =>106  res.json({ ok: true, usage: 'POST /pose/predict {landmarks:[[x,y]x17], threshold?} or {imageUrl|imageB64}' })107);108 109app.post("/pose/predict", async (req, res) => {110  const parsed = PredictBody.safeParse(req.body);111  if (!parsed.success) {112    return res.status(400).json({ error: "bad_request", details: parsed.error.errors });113  }114  const { landmarks, imageUrl, imageB64, threshold } = parsed.data;115 116  try {117    if (landmarks) {118      const out = await classifyLandmarks(landmarks, threshold ?? 0.8);119      return res.json(out);120    }121    if (imageUrl || imageB64) {122      const lm = await detectLandmarksFromImage({ imageUrl, imageB64 });123      const out = await classifyLandmarks(lm, threshold ?? 0.8);124      return res.json(out);125    }126    return res.status(400).json({ error: "bad_request", message: "Provide landmarks or imageUrl/imageB64" });127  } catch (e: any) {128    console.error("predict error:", e);129    return res.status(500).json({ error: "inference_failed", message: e?.message ?? String(e) });130  }131});132 133// NEW: verify a specific pose using the classifier probability for that pose134app.post("/pose/verify", async (req, res) => {135  const parsed = VerifyBody.safeParse(req.body);136  if (!parsed.success) {137    return res.status(400).json({ error: "bad_request", details: parsed.error.errors });138  }139  const { expectedPose, imageUrl, imageB64, threshold } = parsed.data;140  const th = threshold ?? 0.8;141 142  try {143    const landmarks = await detectLandmarksFromImage({ imageUrl, imageB64 });144    const result = await classifyLandmarks(landmarks, th);145 146    const expectedScore = Number(result.scores?.[expectedPose] ?? 0);147    const passed = expectedScore >= th;148 149    return res.json({150      ok: true,151      expectedPose,152      referenceUrl: REF_IMAGES[expectedPose],153      threshold: th,154      passed,155      expectedScore,156      top: result.top,157      scores: result.scores,158    });159  } catch (e: any) {160    console.error("verify error:", e);161    return res.status(500).json({ error: "inference_failed", message: e?.message ?? String(e) });162  }163});164 165// ---------------- Start ----------------166const port = Number(process.env.PORT || 7860);167 168initTf()169  .catch((e) => console.warn("initTf failed (will lazily init later):", e))170  .finally(() => {171    app.listen(port, "0.0.0.0", () => {172      console.log(`poseflow_backend listening on http://0.0.0.0:${port}`);173    });174  });175 176export default app;