| # 🪄 API Reference — ID Photo Retoucher |
|
|
| **Base URL:** `https://esmailx51-retoucher.hf.space` |
| **Gradio API Prefix:** `/gradio_api` |
| **API Endpoint:** `/gradio_api/run/retouch` |
| **Interactive Docs:** [View on Hugging Face](https://huggingface.co/spaces/esmailx51/retoucher?view=api) |
|
|
| --- |
|
|
| ## Overview |
|
|
| The retoucher exposes its interface and programmatic prediction endpoints through Gradio. |
|
|
| > [!WARNING] |
| > **Hugging Face Spaces Queue Requirement (Gradio 5):** |
| > Since this Space runs on ZeroGPU with queueing enabled, direct stateless HTTP `POST` requests (using raw `requests` or `cURL`) to the `/gradio_api/run/retouch` endpoint are blocked with a `404 Not Found` error (`"This API endpoint does not accept direct HTTP POST requests. Please join the queue to use this API."`). |
| > |
| > To consume the API programmatically, **you must use the official client libraries** (`gradio_client` in Python or `@gradio/client` in JavaScript/TypeScript) as shown in Section 1 and Section 3. The clients automatically handle the SSE queue handshake and state tracking. Raw HTTP/cURL calls will only work in local development environments where the queue is disabled. |
|
|
| | Field | Value | |
| |-------|-------| |
| | Endpoint (Local/No Queue) | `POST /gradio_api/run/retouch` | |
| | Upload Endpoint (Local/No Queue) | `POST /gradio_api/upload` | |
| | Queue | Enabled (requests are queued) | |
| | GPU | ZeroGPU A10G (allocated on-demand per request) | |
| | Max File Size | No limit | |
| | Cold Start | ~30–60s (first request after idle downloads model weights) | |
|
|
|
|
| --- |
|
|
| ## Parameters |
|
|
| | # | Name | Type | Default | Range | Description | |
| |---|------|------|---------|-------|-------------| |
| | 1 | `image` | `Image` | *required* | — | Input face photo (file path, URL, or base64) | |
| | 2 | `strength` | `float` | `0.6` | `0.0 – 1.0` | Skin retouching intensity. 0 = no change, 1 = full GFPGAN restoration | |
| | 3 | `undereye_strength` | `float` | `0.5` | `0.0 – 1.0` | Under-eye correction intensity. 0 = no correction, 1 = maximum brightening | |
| | 4 | `crease_strength` | `float` | `0.4` | `0.0 – 1.0` | Crease/sag softening. Softens smile lines, marionette folds, double chins | |
| | 5 | `jaw_tighten` | `float` | `0.3` | `0.0 – 1.0` | Jawline tightening. Gently warps/lifts sagging jowls inward | |
| | 6 | `show_masks` | `bool` | `false` | — | If true, returns a debug visualization with mask overlays | |
|
|
| ## Returns |
|
|
| | # | Name | Type | Description | |
| |---|------|------|-------------| |
| | 1 | `retouched_image` | `Image` | The retouched face photo | |
| | 2 | `debug_image` | `Image \| null` | Debug visualization (only if `show_masks=true`). Green = skin, Orange = under-eye, Blue = wrinkle zones | |
|
|
| --- |
|
|
| ## Usage Examples |
|
|
| ### 1. Python — Gradio Client (Recommended) |
|
|
| The easiest way. Install with `pip install gradio_client`. |
|
|
| ```python |
| from gradio_client import Client |
| |
| # Connect to the Space |
| client = Client("esmailx51/retoucher") |
| |
| # Basic usage — just retouch with defaults |
| result = client.predict( |
| "photo.jpg", # local file path |
| 0.6, # skin strength |
| 0.5, # undereye strength |
| 0.4, # crease strength |
| 0.3, # jaw tighten |
| False, # show debug masks |
| api_name="/retouch" |
| ) |
| |
| retouched_path = result[0] # path to the retouched image |
| debug_path = result[1] # None (since show_masks=False) |
| print(f"Retouched image saved to: {retouched_path}") |
| ``` |
|
|
| #### From a URL instead of a local file |
|
|
| ```python |
| from gradio_client import Client, handle_file |
| |
| client = Client("esmailx51/retoucher") |
| |
| result = client.predict( |
| handle_file("https://example.com/face.jpg"), |
| 0.7, 0.5, 0.4, 0.3, False, |
| api_name="/retouch" |
| ) |
| ``` |
|
|
| #### Aggressive retouching (older subjects) |
|
|
| ```python |
| result = client.predict( |
| "photo.jpg", |
| 0.8, # strong skin smoothing |
| 0.7, # strong undereye correction |
| 0.7, # strong wrinkle reduction |
| 0.5, # strong jaw tightening |
| False, |
| api_name="/retouch" |
| ) |
| ``` |
|
|
| #### Minimal retouching (young subjects) |
|
|
| ```python |
| result = client.predict( |
| "photo.jpg", |
| 0.3, # light skin smoothing |
| 0.2, # light undereye |
| 0.1, # minimal wrinkle reduction |
| 0.0, # no jaw tightening |
| False, |
| api_name="/retouch" |
| ) |
| ``` |
|
|
| #### With debug masks |
|
|
| ```python |
| result = client.predict( |
| "photo.jpg", |
| 0.6, 0.5, 0.4, 0.3, |
| True, # show debug masks |
| api_name="/retouch" |
| ) |
| retouched = result[0] |
| debug_vis = result[1] # side-by-side: masks overlay | retouched result |
| ``` |
|
|
| --- |
|
|
| ### 2. Python — requests (Raw HTTP) |
|
|
| > [!CAUTION] |
| > **Local/No-Queue only:** This raw HTTP request method will return a `404 / Please join the queue` error on the hosted Hugging Face Space. It only works in local development environments where the Gradio queue is disabled. For hosted Spaces, you must use the `gradio_client` library (Section 1). |
| |
| ```python |
| import requests |
| import json |
| from pathlib import Path |
| |
| # In Gradio 5, endpoints are prefixed with /gradio_api |
| BASE_URL = "http://127.0.0.1:7860/gradio_api" |
|
|
| # Step 1: Upload the image to the local Gradio server |
| with open("photo.jpg", "rb") as f: |
| upload_resp = requests.post( |
| f"{BASE_URL}/upload", |
| files={"files": ("photo.jpg", f, "image/jpeg")} |
| ) |
| uploaded_files = upload_resp.json() |
| file_path = uploaded_files[0] # server-side temp path |
| |
| # Step 2: Call the local API prediction endpoint (/run/retouch) |
| payload = { |
| "data": [ |
| {"path": file_path, "meta": {"_type": "gradio.FileData"}}, |
| 0.6, # skin strength |
| 0.5, # undereye strength |
| 0.4, # crease strength |
| 0.3, # jaw tighten |
| False # show masks |
| ] |
| } |
| |
| resp = requests.post(f"{BASE_URL}/run/retouch", json=payload) |
| result = resp.json() |
| |
| # Step 3: Extract & download the result |
| # In Gradio 5, the direct response maps outputs to keys like "output" (or "data" fallback) |
| if "data" in result: |
| retouched_url = result["data"][0]["url"] |
| else: |
| retouched_url = result["output"]["url"] |
| |
| img_data = requests.get(retouched_url).content |
| Path("retouched.jpg").write_bytes(img_data) |
| print("Saved retouched.jpg") |
| ``` |
| |
| --- |
| |
| ### 3. JavaScript / TypeScript |
| |
| Install with `npm install @gradio/client`. |
| |
| ```javascript |
| import { Client } from "@gradio/client"; |
|
|
| const client = await Client.connect("esmailx51/retoucher"); |
|
|
| const result = await client.predict("/retouch", { |
| image: "https://example.com/face.jpg", // URL or Blob |
| strength: 0.6, |
| undereye_strength: 0.5, |
| crease_strength: 0.4, |
| jaw_tighten: 0.3, |
| show_masks: false, |
| }); |
| |
| console.log(result.data); |
| // [{ url: "https://...retouched.webp", ... }, null] |
| ``` |
| |
| #### With a local file (Node.js) |
| |
| ```javascript |
| import { Client, handle_file } from "@gradio/client"; |
| import fs from "fs"; |
| |
| const client = await Client.connect("esmailx51/retoucher"); |
| |
| const result = await client.predict("/retouch", { |
| image: handle_file("./photo.jpg"), |
| strength: 0.6, |
| undereye_strength: 0.5, |
| crease_strength: 0.4, |
| jaw_tighten: 0.3, |
| show_masks: false, |
| }); |
| |
| // Download the retouched image |
| const imageUrl = result.data[0].url; |
| const response = await fetch(imageUrl); |
| const buffer = Buffer.from(await response.arrayBuffer()); |
| fs.writeFileSync("retouched.jpg", buffer); |
| ``` |
| |
| #### Browser (Fetch from a file input) |
| |
| ```javascript |
| import { Client } from "@gradio/client"; |
|
|
| async function retouchPhoto(fileInput) { |
| const client = await Client.connect("esmailx51/retoucher"); |
| const file = fileInput.files[0]; |
| |
| const result = await client.predict("/retouch", { |
| image: new Blob([await file.arrayBuffer()], { type: file.type }), |
| strength: 0.6, |
| undereye_strength: 0.5, |
| crease_strength: 0.4, |
| jaw_tighten: 0.3, |
| show_masks: false, |
| }); |
| |
| // Display the result |
| const imgUrl = result.data[0].url; |
| document.getElementById("result").src = imgUrl; |
| } |
| ``` |
| |
| --- |
|
|
| ### 4. cURL |
|
|
| > [!CAUTION] |
| > **Local/No-Queue only:** This cURL method will return a `404 / Please join the queue` error on the hosted Hugging Face Space. It only works in local development environments where the Gradio queue is disabled. For hosted Spaces, you must use the official client libraries (Section 1 & 3). |
|
|
| ```bash |
| # Step 1: Upload the image (pointing to local Gradio 5 API prefix) |
| FILE_PATH=$(curl -s -X POST \ |
| "http://127.0.0.1:7860/gradio_api/upload" \ |
| -F "files=@photo.jpg" \ |
| | jq -r '.[0]') |
| |
| # Step 2: Call the API prediction endpoint |
| curl -s -X POST \ |
| "http://127.0.0.1:7860/gradio_api/run/retouch" \ |
| -H "Content-Type: application/json" \ |
| -d "{ |
| \"data\": [ |
| {\"path\": \"$FILE_PATH\", \"meta\": {\"_type\": \"gradio.FileData\"}}, |
| 0.6, |
| 0.5, |
| 0.4, |
| 0.3, |
| false |
| ] |
| }" | jq -r '.output.url' |
| ``` |
|
|
| --- |
|
|
| ## Batch Processing (Python) |
|
|
| Process multiple images in sequence: |
|
|
| ```python |
| from gradio_client import Client |
| from pathlib import Path |
| |
| client = Client("esmailx51/retoucher") |
| input_dir = Path("./input_photos") |
| output_dir = Path("./retouched") |
| output_dir.mkdir(exist_ok=True) |
| |
| for img_path in input_dir.glob("*.jpg"): |
| print(f"Processing {img_path.name}...") |
| result = client.predict( |
| str(img_path), |
| 0.6, 0.5, 0.4, 0.3, False, |
| api_name="/retouch" |
| ) |
| # Copy result to output directory |
| retouched = Path(result[0]) |
| retouched.rename(output_dir / img_path.name) |
| print(f" → Saved to {output_dir / img_path.name}") |
| ``` |
|
|
| --- |
|
|
| ## Integration Example: Flask Backend |
|
|
| ```python |
| from flask import Flask, request, send_file |
| from gradio_client import Client |
| import tempfile, shutil |
| |
| app = Flask(__name__) |
| gr_client = Client("esmailx51/retoucher") |
| |
| @app.route("/retouch", methods=["POST"]) |
| def retouch(): |
| # Save uploaded file |
| photo = request.files["photo"] |
| strength = float(request.form.get("strength", 0.6)) |
| undereye = float(request.form.get("undereye", 0.5)) |
| creases = float(request.form.get("creases", 0.4)) |
| jaw = float(request.form.get("jaw", 0.3)) |
| |
| with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp: |
| photo.save(tmp.name) |
| result = gr_client.predict( |
| tmp.name, |
| strength, undereye, creases, jaw, False, |
| api_name="/retouch" |
| ) |
| return send_file(result[0], mimetype="image/webp") |
| |
| if __name__ == "__main__": |
| app.run(port=5000) |
| ``` |
|
|
| --- |
|
|
| ## Error Handling |
|
|
| ```python |
| from gradio_client import Client |
| from gradio_client.exceptions import AppError |
| |
| client = Client("esmailx51/retoucher") |
| |
| try: |
| result = client.predict( |
| "photo.jpg", 0.6, 0.5, 0.4, 0.3, False, |
| api_name="/retouch" |
| ) |
| except AppError as e: |
| if "No face detected" in str(e): |
| print("No face found in the image") |
| elif "queue is full" in str(e): |
| print("Server is busy, try again later") |
| else: |
| print(f"API error: {e}") |
| except ConnectionError: |
| print("Space is sleeping — the first request wakes it up (~60s)") |
| ``` |
|
|
| --- |
|
|
| ## Tuning Guide |
|
|
| | Goal | strength | undereye | creases | jaw | |
| |------|----------|----------|---------|-----| |
| | **Natural / Minimal** | 0.3 | 0.2 | 0.1 | 0.0 | |
| | **Balanced (default)** | 0.6 | 0.5 | 0.4 | 0.3 | |
| | **Aggressive (older subjects)** | 0.8 | 0.7 | 0.7 | 0.5 | |
| | **Skin only (no anti-aging)** | 0.6 | 0.0 | 0.0 | 0.0 | |
| | **Under-eye only** | 0.0 | 0.7 | 0.0 | 0.0 | |
|
|
| --- |
|
|
| ## Rate Limits & Notes |
|
|
| - **Cold start**: If the Space has been idle, the first request takes ~30–60 seconds (model download + loading). Subsequent requests are fast (~5–10s on GPU). |
| - **Queue**: Requests are queued. If the Space is busy, your request will wait in line. |
| - **GPU timeout**: ZeroGPU allocates GPU per-request. Very large images may time out. |
| - **Output format**: Retouched images are returned as PNG by default to maintain high-resolution lossless quality. |
| - **No face detected**: If no face is found, the original image is returned unchanged. |
|
|