controlapi / app.py
economo's picture
Initial controlapi: pose extraction with MediaPipe for scenario position visualization
c01bac0
Raw
History Blame Contribute Delete
32.2 kB
"""
ControlAPI β€” Pose extraction & skeleton visualization API
CPU-only, lightweight. Uses MediaPipe Pose for body landmark detection.
Endpoints:
POST /api/v1/pose β€” Extract pose skeleton from uploaded image
POST /api/v1/pose/url β€” Extract pose skeleton from image URL
POST /api/v1/pose/batch β€” Extract poses from multiple image URLs
GET /api/v1/scenarios β€” List all scenarios with their prompts
GET /health β€” Health check
"""
from __future__ import annotations
import base64
import io
import logging
import os
from typing import Optional
import cv2
import mediapipe as mp
import numpy as np
import requests as http_requests
from fastapi import FastAPI, File, Form, UploadFile, HTTPException, Depends, Security
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, Response
from fastapi.security import APIKeyHeader
from PIL import Image
# ── Logging ──────────────────────────────────────────────────────────────────
logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s", level=logging.INFO)
log = logging.getLogger("controlapi")
# ── API Key Auth ─────────────────────────────────────────────────────────────
API_KEY = os.environ.get("API_KEY", "")
_api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
async def require_api_key(api_key: str = Security(_api_key_header)):
if not API_KEY:
return api_key # no key configured = open access
if not api_key or api_key != API_KEY:
raise HTTPException(401, "Invalid or missing API key")
return api_key
# ── MediaPipe Pose ───────────────────────────────────────────────────────────
mp_pose = mp.solutions.pose
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
# Custom drawing spec for clear skeleton visualization
LANDMARK_SPEC = mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=3, circle_radius=4)
CONNECTION_SPEC = mp_drawing.DrawingSpec(color=(0, 200, 255), thickness=2)
# ── Scenarios (same as telegram bot) ─────────────────────────────────────────
SCENARIOS = {
"sofa_anal_blonde": {
"label": "Sofa anal - Blonde",
"position": "Anal cowgirl, wide open legs, man holding legs, lying on sofa",
"prompt": "((blonde woman)), leash, ((wide open legs)), ((getting very thick cock ass penetration)), ((very thick penis penetrating deep ass from below)), (((sitting in a modern white leather sofa))), ((deep ass penetration with very thick cock)), 1boy, hetero, ((man taking legs)), nipples, anal, cum, pussy, blue_eyes, thighhighs, cum_in_ass, deep penetration sex, ginger full bangs hair, uncensored, spread_legs, lying, collar, pubic_hair, looking_at_viewer, large-size_breasts, long_hair, black_nails, solo_focus, pov, makeup, viewer_holding_leash, nail_polish, lips, ((interlaced ankle strap very high heels pumps round toes)), ((very transparent white stockings)), ((very high pink leather heels, criss cross pumps)), (denim pleated mini skirt), ((dark purple corset laces up)), sensual look, (perfect ass penetration image), ((man is white skin)), ((full body view))",
},
"sofa_anal_brunette": {
"label": "Sofa anal - Brunette",
"position": "Anal cowgirl, wide open legs, man holding legs, lying on sofa",
"prompt": "((brunette woman)), leash, ((wide open legs)), ((getting very thick cock ass penetration)), ((very thick penis penetrating deep ass from below)), (((sitting in a modern white leather sofa))), ((deep ass penetration with very thick cock)), 1boy, hetero, ((man taking legs)), nipples, anal, cum, pussy, green_eyes, thighhighs, cum_in_ass, deep penetration sex, long wavy brown hair, uncensored, spread_legs, lying, collar, pubic_hair, looking_at_viewer, large-size_breasts, long_hair, black_nails, solo_focus, pov, makeup, viewer_holding_leash, nail_polish, lips, ((interlaced ankle strap very high heels pumps round toes)), ((very transparent black stockings)), ((very high red leather heels, criss cross pumps)), (leather mini skirt), ((red corset laces up)), sensual look, (perfect ass penetration image), ((man is white skin)), ((full body view))",
},
"cowgirl_bed": {
"label": "Cowgirl ride - Bed",
"position": "Cowgirl vaginal riding, wide spread legs, on luxury bed",
"prompt": "((gorgeous woman)), ((riding very thick cock cowgirl position)), ((deep vaginal penetration)), ((very thick penis inside pussy)), 1boy, hetero, ((wide spread legs)), large-size_breasts, long_hair, uncensored, pussy, cum, cum_in_pussy, ((on luxury bed)), nipples, pubic_hair, looking_at_viewer, sensual look, makeup, ((very high heels)), ((transparent stockings)), thighhighs, collar, leash, pov, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"doggy_bedroom": {
"label": "Doggy style - Bedroom",
"position": "Doggy style, ass up, from behind, looking back",
"prompt": "((gorgeous woman)), ((doggy style position)), ((getting very thick cock deep penetration from behind)), ((very thick penis penetrating deep)), 1boy, hetero, ((ass up)), ((wide spread legs)), large-size_breasts, long_hair, uncensored, looking_back_at_viewer, cum, pussy, anal, nipples, pubic_hair, ((luxury bedroom)), sensual look, makeup, ((very high heels)), thighhighs, ((transparent stockings)), collar, leash, nail_polish, ((full body view)), 8k, masterpiece",
},
"blowjob_pov": {
"label": "Blowjob POV",
"position": "On knees, blowjob, looking up at viewer",
"prompt": "((beautiful woman)), ((giving blowjob pov)), ((very thick cock in mouth)), ((deepthroat)), 1boy, ((on knees)), large-size_breasts, long_hair, makeup, looking_at_viewer, uncensored, sensual eyes, collar, leash, lipstick, nail_polish, ((very high heels)), thighhighs, ((transparent stockings)), pubic_hair, nipples, ((close up face and body)), 8k, masterpiece",
},
"missionary_sofa": {
"label": "Missionary - Sofa",
"position": "Missionary on sofa, wide open legs held up, POV",
"prompt": "((beautiful woman)), ((missionary position on sofa)), ((getting very thick cock deep vaginal penetration)), ((very thick penis penetrating deep pussy)), ((wide open legs held up)), 1boy, hetero, large-size_breasts, long_hair, uncensored, cum, cum_in_pussy, pussy, nipples, pubic_hair, looking_at_viewer, ((modern white leather sofa)), sensual look, makeup, ((very high heels)), thighhighs, ((transparent stockings)), collar, leash, viewer_holding_leash, nail_polish, pov, ((full body view)), 8k, masterpiece",
},
"anal_doggy_office": {
"label": "Anal doggy - Office",
"position": "Bent over office desk, anal from behind, ass up",
"prompt": "((sexy woman)), ((bent over office desk)), ((getting very thick cock deep ass penetration from behind)), ((very thick penis penetrating deep ass)), 1boy, hetero, ((ass up)), anal, cum_in_ass, large-size_breasts, long_hair, uncensored, spread_legs, looking_back_at_viewer, nipples, pubic_hair, pussy, ((modern office)), sensual look, makeup, ((very high heels)), thighhighs, ((transparent stockings)), collar, ((pencil skirt pulled up)), nail_polish, ((full body view)), 8k, masterpiece",
},
"double_penetration": {
"label": "Double penetration",
"position": "DP, 2 men, wide spread legs, luxury bedroom",
"prompt": "((gorgeous woman)), ((double penetration)), ((very thick cock in pussy and ass simultaneously)), 2boys, hetero, ((wide spread legs)), large-size_breasts, long_hair, uncensored, cum, cum_in_pussy, cum_in_ass, anal, pussy, nipples, pubic_hair, looking_at_viewer, ((luxury bedroom)), sensual look, makeup, ((very high heels)), thighhighs, ((transparent stockings)), collar, leash, nail_polish, ((full body view)), 8k, masterpiece",
},
"lenceria_solo": {
"label": "Lenceria solo",
"position": "Solo, spread legs on bed, elegant lingerie, seductive pose",
"prompt": "((beautiful woman)), ((solo)), ((elegant lace lingerie)), ((garter belt)), ((transparent stockings)), ((very high heels)), large-size_breasts, long_hair, uncensored, pussy, nipples, pubic_hair, looking_at_viewer, ((luxury bedroom)), ((spread legs on bed)), sensual look, seductive pose, makeup, collar, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"reverse_cowgirl_pool": {
"label": "Reverse cowgirl - Pool",
"position": "Reverse cowgirl anal, wide spread legs facing away, looking back",
"prompt": "((gorgeous woman)), ((clear visible face)), ((reverse cowgirl riding very thick cock)), ((deep anal penetration)), ((very thick penis deep inside ass)), 1boy, hetero, ((wide spread legs facing away)), large-size_breasts, long_hair, blonde_hair, blue_eyes, uncensored, cum, cum_in_ass, anal, pussy, nipples, pubic_hair, ((looking back at viewer with seductive expression)), ((face clearly visible)), ((luxury pool lounge chair)), sensual look, makeup, ((very high clear platform heels)), thighhighs, ((transparent white stockings)), collar, leash, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"standing_wall_hotel": {
"label": "Standing wall - Hotel",
"position": "Standing against wall, one leg raised held by man, fucked from behind",
"prompt": "((stunning woman)), ((clear visible face)), ((standing against wall getting fucked from behind)), ((one leg raised held by man)), ((very thick cock deep vaginal penetration)), 1boy, hetero, large-size_breasts, long brunette hair, green_eyes, uncensored, cum, cum_in_pussy, pussy, nipples, pubic_hair, ((looking at viewer over shoulder)), ((face clearly visible)), ((luxury hotel room)), sensual look, makeup, ((very high red stiletto heels)), thighhighs, ((transparent black stockings)), ((garter belt)), collar, leash, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"lotus_bathtub": {
"label": "Lotus position - Bathtub",
"position": "Lotus/sitting face to face, legs wrapped around, on bathtub edge",
"prompt": "((beautiful woman)), ((clear visible face)), ((lotus position sex)), ((sitting on very thick cock face to face)), ((deep vaginal penetration)), 1boy, hetero, ((legs wrapped around)), large-size_breasts, long red hair, hazel_eyes, uncensored, cum, cum_in_pussy, pussy, nipples, pubic_hair, ((looking directly at viewer)), ((face clearly visible)), ((luxury marble bathtub edge)), sensual look, makeup, ((very high gold heels)), thighhighs, ((transparent nude stockings)), collar, choker, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"spread_eagle_kitchen": {
"label": "Spread eagle - Kitchen",
"position": "Spread eagle on kitchen counter, wide open legs, penetration",
"prompt": "((sexy woman)), ((clear visible face)), ((spread eagle on kitchen counter)), ((getting very thick cock deep penetration)), ((very thick penis penetrating deep pussy)), 1boy, hetero, ((wide open legs)), large-size_breasts, long platinum blonde hair, blue_eyes, uncensored, cum, cum_in_pussy, pussy, nipples, pubic_hair, ((looking at viewer with open mouth)), ((face clearly visible)), ((modern luxury kitchen)), sensual look, makeup, ((very high white heels pumps)), thighhighs, ((transparent stockings)), collar, leash, ((white lace apron open)), nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"bent_over_balcony": {
"label": "Bent over balcony - Penthouse",
"position": "Bent over glass balcony railing, anal from behind, looking back smiling",
"prompt": "((gorgeous woman)), ((clear visible face)), ((bent over glass balcony railing)), ((getting very thick cock deep ass penetration from behind)), ((very thick penis penetrating deep ass)), 1boy, hetero, ((ass up)), anal, cum_in_ass, large-size_breasts, long black hair, brown_eyes, uncensored, spread_legs, ((looking back at viewer smiling)), ((face clearly visible)), ((penthouse balcony city skyline night)), sensual look, makeup, ((very high black patent heels)), thighhighs, ((transparent black stockings)), ((garter belt)), collar, leash, ((tight black dress pulled up)), nail_polish, ((full body view)), 8k, masterpiece",
},
"riding_chair": {
"label": "Riding chair - Living room",
"position": "Riding cowgirl on armchair, wide spread legs, bouncing",
"prompt": "((stunning woman)), ((clear visible face)), ((riding very thick cock on luxury armchair)), ((deep vaginal penetration bouncing)), ((very thick penis deep inside pussy)), 1boy, hetero, ((wide spread legs)), large-size_breasts, long wavy auburn hair, green_eyes, uncensored, cum, cum_in_pussy, pussy, nipples, pubic_hair, ((looking at viewer with pleasure expression)), ((face clearly visible)), ((modern luxury living room)), sensual look, makeup, ((very high leopard print heels)), thighhighs, ((transparent tan stockings)), collar, leash, viewer_holding_leash, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"on_desk_library": {
"label": "On desk - Library",
"position": "Lying back on desk, legs on man's shoulders, vaginal penetration",
"prompt": "((beautiful woman)), ((clear visible face)), ((lying back on wooden desk)), ((getting very thick cock deep vaginal penetration)), ((very thick penis penetrating deep pussy)), ((legs on man shoulders)), 1boy, hetero, large-size_breasts, long honey blonde hair, blue_eyes, uncensored, cum, cum_in_pussy, pussy, nipples, pubic_hair, ((looking up at viewer)), ((face clearly visible)), ((elegant private library bookshelves)), sensual look, makeup, ((very high burgundy heels)), thighhighs, ((transparent white stockings)), ((white blouse open)), ((plaid mini skirt)), collar, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"shower_bathroom": {
"label": "Shower standing - Bathroom",
"position": "Standing in shower, one leg up, wet body, penetration",
"prompt": "((sexy woman)), ((clear visible face)), ((standing one leg up in luxury shower)), ((getting very thick cock deep penetration)), ((very thick penis penetrating deep pussy)), 1boy, hetero, ((wet body)), ((wet hair)), large-size_breasts, long dark hair, brown_eyes, uncensored, cum, cum_in_pussy, pussy, nipples, pubic_hair, ((looking at viewer through glass)), ((face clearly visible)), ((luxury marble shower)), sensual look, makeup, ((very high clear platform heels)), ((water drops on body)), collar, choker, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"throne_fantasy": {
"label": "Throne ride - Fantasy",
"position": "Riding cowgirl on golden throne, dominant expression, crown",
"prompt": "((gorgeous woman)), ((clear visible face)), ((riding very thick cock on golden throne)), ((deep vaginal penetration)), ((very thick penis deep inside)), 1boy, hetero, ((wide spread legs)), large-size_breasts, long silver white hair, violet_eyes, uncensored, cum, cum_in_pussy, pussy, nipples, pubic_hair, ((looking at viewer with dominant expression)), ((face clearly visible)), ((dark luxury throne room candles)), sensual look, makeup, crown, ((very high gold stiletto heels)), thighhighs, ((transparent black stockings)), ((garter belt)), collar, choker, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"legs_up_window": {
"label": "Legs up - Window",
"position": "Lying on daybed, legs up in air held by man, anal penetration",
"prompt": "((stunning woman)), ((clear visible face)), ((lying on daybed legs up in air)), ((getting very thick cock deep anal penetration)), ((very thick penis penetrating deep ass)), 1boy, hetero, ((legs held up by man)), anal, cum_in_ass, large-size_breasts, long caramel hair, hazel_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking at viewer with mouth open)), ((face clearly visible)), ((panoramic window city view sunset)), sensual look, makeup, ((very high pink stiletto heels)), thighhighs, ((transparent white stockings)), ((garter belt)), collar, leash, ((white lace bodysuit open)), nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"anal_pronebone_bed": {
"label": "Anal pronebone - Bed",
"position": "Pronebone (lying flat on stomach), ass raised, anal from above",
"prompt": "((gorgeous woman)), ((clear visible face)), ((pronebone position)), ((very thick cock deep anal penetration from above)), ((very thick penis buried deep inside ass)), 1boy, hetero, ((lying flat on stomach)), ((ass raised)), anal, cum_in_ass, large-size_breasts, long blonde hair, blue_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking back at viewer biting lip)), ((face clearly visible)), ((luxury silk bed sheets)), sensual look, makeup, ((very high silver stiletto heels)), thighhighs, ((transparent white stockings)), ((garter belt)), collar, leash, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"anal_cowgirl_jacuzzi": {
"label": "Anal cowgirl - Jacuzzi",
"position": "Cowgirl anal riding, wide spread legs, in outdoor jacuzzi",
"prompt": "((stunning woman)), ((clear visible face)), ((sitting on very thick cock anal cowgirl)), ((deep ass penetration riding)), ((very thick penis deep inside ass from below)), 1boy, hetero, ((wide spread legs)), anal, cum_in_ass, large-size_breasts, long dark hair, brown_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking at viewer moaning)), ((face clearly visible)), ((luxury outdoor jacuzzi night)), sensual look, makeup, ((very high clear platform heels)), thighhighs, ((transparent black stockings)), collar, choker, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"anal_standing_bathroom": {
"label": "Anal standing lift - Bathroom",
"position": "Full nelson standing, both legs held up by man, anal",
"prompt": "((beautiful woman)), ((clear visible face)), ((standing anal full nelson position)), ((both legs held up by man)), ((very thick cock deep ass penetration)), ((very thick penis penetrating deep ass)), 1boy, hetero, anal, cum_in_ass, large-size_breasts, long red hair, green_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking at viewer with pleasure face)), ((face clearly visible)), ((luxury marble bathroom mirror)), sensual look, makeup, ((very high red stiletto heels)), thighhighs, ((transparent nude stockings)), collar, leash, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"anal_reverse_piano": {
"label": "Anal reverse - Piano",
"position": "Reverse cowgirl anal on piano bench, leaning back, wide legs",
"prompt": "((gorgeous woman)), ((clear visible face)), ((reverse cowgirl anal on piano bench)), ((very thick cock deep inside ass)), ((very thick penis deep anal penetration from below)), 1boy, hetero, ((wide spread legs)), ((leaning back)), anal, cum_in_ass, large-size_breasts, long platinum blonde hair, blue_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking back at viewer seductive)), ((face clearly visible)), ((elegant room grand piano)), sensual look, makeup, ((very high black patent heels)), thighhighs, ((transparent black stockings)), ((garter belt)), ((black lace corset)), collar, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"anal_piledriver_penthouse": {
"label": "Anal pile driver - Penthouse",
"position": "Pile driver, legs over head, anal penetration downward",
"prompt": "((sexy woman)), ((clear visible face)), ((pile driver position)), ((legs over head)), ((very thick cock deep anal penetration downward)), ((very thick penis deep inside ass)), 1boy, hetero, anal, cum_in_ass, large-size_breasts, long brunette hair, hazel_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking at viewer upside down)), ((face clearly visible)), ((luxury penthouse fur rug)), sensual look, makeup, ((very high white stiletto heels)), thighhighs, ((transparent white stockings)), collar, choker, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"anal_sideways_couch": {
"label": "Anal sideways - Couch",
"position": "Lying sideways on couch, one leg raised high, anal from behind",
"prompt": "((beautiful woman)), ((clear visible face)), ((lying sideways on leather couch)), ((one leg raised high)), ((very thick cock deep anal penetration from behind)), ((very thick penis deep inside ass)), 1boy, hetero, anal, cum_in_ass, large-size_breasts, long wavy black hair, brown_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking at viewer with open mouth)), ((face clearly visible)), ((modern dark leather couch)), sensual look, makeup, ((very high leopard print heels)), thighhighs, ((transparent tan stockings)), ((garter belt)), collar, leash, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"anal_table_dining": {
"label": "Anal table - Dining room",
"position": "Bent over glass dining table, anal from behind, ass up high",
"prompt": "((stunning woman)), ((clear visible face)), ((bent over glass dining table)), ((very thick cock deep anal penetration from behind)), ((very thick penis buried deep in ass)), 1boy, hetero, ((ass up high)), ((legs spread wide)), anal, cum_in_ass, large-size_breasts, long honey blonde hair, green_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking back at viewer smiling)), ((face clearly visible)), ((luxury dining room chandelier)), sensual look, makeup, ((very high gold heels)), thighhighs, ((transparent nude stockings)), ((tight red dress pulled up)), collar, leash, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"anal_carry_staircase": {
"label": "Anal carry - Staircase",
"position": "Carried in air by man, legs spread wide held up, standing anal",
"prompt": "((gorgeous woman)), ((clear visible face)), ((carried in air by man)), ((legs spread wide held up)), ((very thick cock deep anal penetration while lifted)), ((very thick penis deep inside ass)), 1boy, hetero, anal, cum_in_ass, large-size_breasts, long auburn hair, blue_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking at viewer with ecstasy expression)), ((face clearly visible)), ((luxury mansion marble staircase)), sensual look, makeup, ((very high pink stiletto heels)), thighhighs, ((transparent white stockings)), ((garter belt)), collar, choker, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"anal_lap_sports_car": {
"label": "Anal lap - Sports car",
"position": "Anal lap sitting, wide spread legs, in sports car back seat",
"prompt": "((sexy woman)), ((clear visible face)), ((sitting on very thick cock anal lap position)), ((deep ass penetration sitting)), ((very thick penis deep inside ass from below)), 1boy, hetero, ((wide spread legs)), anal, cum_in_ass, large-size_breasts, long silver hair, violet_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking at viewer with mouth open)), ((face clearly visible)), ((luxury sports car back seat)), sensual look, makeup, ((very high chrome heels)), thighhighs, ((transparent beige stockings)), ((leather harness)), collar, leash, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
"anal_missionary_rooftop": {
"label": "Anal missionary - Rooftop",
"position": "Missionary anal, legs held up wide by man, rooftop at night",
"prompt": "((beautiful woman)), ((clear visible face)), ((missionary anal position)), ((legs held up wide by man)), ((very thick cock deep anal penetration)), ((very thick penis penetrating deep ass)), 1boy, hetero, ((wide open legs)), anal, cum_in_ass, large-size_breasts, long caramel hair, hazel_eyes, uncensored, cum, pussy, nipples, pubic_hair, ((looking at viewer begging expression)), ((face clearly visible)), ((luxury rooftop lounge night city lights)), sensual look, makeup, ((very high burgundy stiletto heels)), thighhighs, ((transparent white stockings)), ((garter belt)), collar, leash, viewer_holding_leash, nail_polish, lips, ((full body view)), 8k, masterpiece",
},
}
# ── FastAPI App ──────────────────────────────────────────────────────────────
app = FastAPI(title="ControlAPI", description="Pose extraction & skeleton visualization")
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
def _load_image(data: bytes) -> np.ndarray:
"""Load image bytes into BGR numpy array for MediaPipe."""
img = Image.open(io.BytesIO(data)).convert("RGB")
return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
def _extract_pose(img_bgr: np.ndarray, draw_on_image: bool = False) -> tuple[bytes | None, dict]:
"""
Extract pose landmarks and draw skeleton.
Returns (skeleton_png_bytes, landmarks_dict).
If draw_on_image=True, draws skeleton on original image.
If False, draws on black background (clean stick figure).
"""
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
h, w = img_bgr.shape[:2]
with mp_pose.Pose(
static_image_mode=True,
model_complexity=2, # highest accuracy
enable_segmentation=False,
min_detection_confidence=0.5,
) as pose:
results = pose.process(img_rgb)
if not results.pose_landmarks:
return None, {"detected": False, "landmarks": []}
# Draw skeleton
if draw_on_image:
canvas = img_bgr.copy()
else:
canvas = np.zeros((h, w, 3), dtype=np.uint8)
mp_drawing.draw_landmarks(
canvas,
results.pose_landmarks,
mp_pose.POSE_CONNECTIONS,
landmark_drawing_spec=LANDMARK_SPEC,
connection_drawing_spec=CONNECTION_SPEC,
)
# Add joint labels for key points
key_joints = {
0: "nose", 11: "L_shoulder", 12: "R_shoulder",
13: "L_elbow", 14: "R_elbow", 15: "L_wrist", 16: "R_wrist",
23: "L_hip", 24: "R_hip", 25: "L_knee", 26: "R_knee",
27: "L_ankle", 28: "R_ankle",
}
landmarks_list = []
for idx, lm in enumerate(results.pose_landmarks.landmark):
lm_data = {
"id": idx,
"x": round(lm.x, 4),
"y": round(lm.y, 4),
"z": round(lm.z, 4),
"visibility": round(lm.visibility, 4),
}
if idx in key_joints:
lm_data["name"] = key_joints[idx]
# Draw label on canvas
px, py = int(lm.x * w), int(lm.y * h)
cv2.putText(canvas, key_joints[idx], (px + 5, py - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.35, (255, 255, 255), 1)
landmarks_list.append(lm_data)
# Encode to PNG
_, buf = cv2.imencode(".png", canvas)
return buf.tobytes(), {"detected": True, "landmarks": landmarks_list}
def _download_image(url: str) -> bytes:
"""Download image from URL."""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
resp = http_requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
return resp.content
# ── Endpoints ────────────────────────────────────────────────────────────────
@app.post("/api/v1/pose")
async def extract_pose_upload(
image: UploadFile = File(...),
overlay: bool = Form(False),
_key: str = Depends(require_api_key),
):
"""Extract pose skeleton from uploaded image.
overlay=True draws skeleton on top of original image.
overlay=False (default) draws on black background.
Returns PNG image.
"""
data = await image.read()
img_bgr = _load_image(data)
skeleton_png, landmarks = _extract_pose(img_bgr, draw_on_image=overlay)
if skeleton_png is None:
raise HTTPException(422, "No human pose detected in image")
return Response(content=skeleton_png, media_type="image/png")
@app.post("/api/v1/pose/url")
async def extract_pose_url(
image_url: str = Form(...),
overlay: bool = Form(False),
return_format: str = Form("image"),
_key: str = Depends(require_api_key),
):
"""Extract pose from image URL.
return_format: 'image' returns PNG, 'json' returns landmarks, 'both' returns base64 + landmarks.
"""
try:
data = _download_image(image_url)
except Exception as e:
raise HTTPException(400, f"Failed to download image: {e}")
img_bgr = _load_image(data)
skeleton_png, landmarks = _extract_pose(img_bgr, draw_on_image=overlay)
if skeleton_png is None:
if return_format == "json":
return JSONResponse({"detected": False, "landmarks": []})
raise HTTPException(422, "No human pose detected in image")
if return_format == "json":
return JSONResponse(landmarks)
elif return_format == "both":
b64 = base64.b64encode(skeleton_png).decode()
return JSONResponse({
"skeleton_b64": b64,
**landmarks,
})
else:
return Response(content=skeleton_png, media_type="image/png")
@app.post("/api/v1/pose/batch")
async def extract_pose_batch(
image_urls: str = Form(...),
overlay: bool = Form(False),
_key: str = Depends(require_api_key),
):
"""Extract poses from multiple image URLs (comma-separated or JSON array).
Returns JSON with base64 skeletons for each image.
"""
# Parse URLs
urls = image_urls.strip()
if urls.startswith("["):
import json
url_list = json.loads(urls)
else:
url_list = [u.strip() for u in urls.split(",") if u.strip()]
if len(url_list) > 30:
raise HTTPException(400, "Maximum 30 images per batch")
results = []
for i, url in enumerate(url_list):
try:
data = _download_image(url)
img_bgr = _load_image(data)
skeleton_png, landmarks = _extract_pose(img_bgr, draw_on_image=overlay)
if skeleton_png:
b64 = base64.b64encode(skeleton_png).decode()
results.append({
"index": i,
"url": url,
"detected": True,
"skeleton_b64": b64,
"key_landmarks": {
lm["name"]: {"x": lm["x"], "y": lm["y"], "visibility": lm["visibility"]}
for lm in landmarks["landmarks"] if "name" in lm
},
})
else:
results.append({"index": i, "url": url, "detected": False})
except Exception as e:
results.append({"index": i, "url": url, "detected": False, "error": str(e)})
log.warning(f"Batch pose {i}: {e}")
return JSONResponse({"total": len(url_list), "detected": sum(1 for r in results if r.get("detected")), "results": results})
@app.get("/api/v1/scenarios")
async def list_scenarios(_key: str = Depends(require_api_key)):
"""List all available scenarios with position descriptions and prompts."""
return JSONResponse({
"total": len(SCENARIOS),
"scenarios": {
key: {
"label": s["label"],
"position": s["position"],
"prompt": s["prompt"],
}
for key, s in SCENARIOS.items()
},
})
@app.get("/health")
async def health():
return {"status": "healthy", "service": "controlapi", "pose_engine": "mediapipe"}