Spaces:
Running
on
T4
Running
on
T4
Commit
·
174828e
1
Parent(s):
ff3e3e4
feat(api): add /inpaint-url endpoint returning public download URL; docs updated
Browse files- README.md +3 -2
- api/main.py +39 -3
README.md
CHANGED
|
@@ -19,12 +19,13 @@ Endpoints:
|
|
| 19 |
- `POST /upload-image` (form-data: image=file) → {"id":"<image_id>","filename":"name.png"}
|
| 20 |
- `POST /upload-mask` (form-data: mask=file) → {"id":"<mask_id>","filename":"mask.png"}
|
| 21 |
- `POST /inpaint` (JSON: {image_id, mask_id}) → returns result directly (no download step)
|
|
|
|
| 22 |
- `POST /inpaint-multipart` (form-data: image=file, mask=file) → {"result":"output_xxx.png"}
|
| 23 |
-
- `GET /download/{filename}` → image file (
|
| 24 |
- `GET /logs` → recent uploads/results
|
| 25 |
|
| 26 |
Note:
|
| 27 |
-
-
|
| 28 |
|
| 29 |
Local run:
|
| 30 |
- Install deps: `python3 -m pip install -r requirements.txt`
|
|
|
|
| 19 |
- `POST /upload-image` (form-data: image=file) → {"id":"<image_id>","filename":"name.png"}
|
| 20 |
- `POST /upload-mask` (form-data: mask=file) → {"id":"<mask_id>","filename":"mask.png"}
|
| 21 |
- `POST /inpaint` (JSON: {image_id, mask_id}) → returns result directly (no download step)
|
| 22 |
+
- `POST /inpaint-url` (JSON: {image_id, mask_id}) → {"result":"output_xxx.png","url":"https://.../download/output_xxx.png"}
|
| 23 |
- `POST /inpaint-multipart` (form-data: image=file, mask=file) → {"result":"output_xxx.png"}
|
| 24 |
+
- `GET /download/{filename}` → image file (public; optional for ID-based inpaint)
|
| 25 |
- `GET /logs` → recent uploads/results
|
| 26 |
|
| 27 |
Note:
|
| 28 |
+
- `POST /inpaint` returns image bytes directly. Use `POST /inpaint-url` if you need a shareable URL in the response.
|
| 29 |
|
| 30 |
Local run:
|
| 31 |
- Install deps: `python3 -m pip install -r requirements.txt`
|
api/main.py
CHANGED
|
@@ -5,7 +5,7 @@ from datetime import datetime
|
|
| 5 |
from typing import Dict, List, Optional
|
| 6 |
|
| 7 |
import numpy as np
|
| 8 |
-
from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, Header
|
| 9 |
from fastapi.responses import FileResponse, JSONResponse
|
| 10 |
from pydantic import BaseModel
|
| 11 |
from PIL import Image
|
|
@@ -148,10 +148,33 @@ def inpaint(req: InpaintRequest, _: None = Depends(bearer_auth)):
|
|
| 148 |
return FileResponse(result_path, media_type="image/png", filename=result_name)
|
| 149 |
|
| 150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
@app.post("/inpaint-multipart")
|
| 152 |
def inpaint_multipart(
|
| 153 |
image: UploadFile = File(...),
|
| 154 |
mask: UploadFile = File(...),
|
|
|
|
| 155 |
_: None = Depends(bearer_auth),
|
| 156 |
) -> Dict[str, str]:
|
| 157 |
# Load in-memory
|
|
@@ -164,8 +187,21 @@ def inpaint_multipart(
|
|
| 164 |
result_path = os.path.join(OUTPUT_DIR, result_name)
|
| 165 |
Image.fromarray(result).save(result_path)
|
| 166 |
|
| 167 |
-
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
|
| 170 |
|
| 171 |
@app.get("/download/{filename}")
|
|
|
|
| 5 |
from typing import Dict, List, Optional
|
| 6 |
|
| 7 |
import numpy as np
|
| 8 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, Header, Request
|
| 9 |
from fastapi.responses import FileResponse, JSONResponse
|
| 10 |
from pydantic import BaseModel
|
| 11 |
from PIL import Image
|
|
|
|
| 148 |
return FileResponse(result_path, media_type="image/png", filename=result_name)
|
| 149 |
|
| 150 |
|
| 151 |
+
@app.post("/inpaint-url")
|
| 152 |
+
def inpaint_url(req: InpaintRequest, request: Request, _: None = Depends(bearer_auth)) -> Dict[str, str]:
|
| 153 |
+
"""Same as /inpaint but returns a JSON with a public download URL instead of image bytes."""
|
| 154 |
+
if req.image_id not in file_store or file_store[req.image_id]["type"] != "image":
|
| 155 |
+
raise HTTPException(status_code=404, detail="image_id not found")
|
| 156 |
+
if req.mask_id not in file_store or file_store[req.mask_id]["type"] != "mask":
|
| 157 |
+
raise HTTPException(status_code=404, detail="mask_id not found")
|
| 158 |
+
|
| 159 |
+
img_rgba = _load_rgba_image(file_store[req.image_id]["path"])
|
| 160 |
+
mask_img = Image.open(file_store[req.mask_id]["path"]) # may be RGB/gray/RGBA
|
| 161 |
+
mask_rgba = _load_rgba_mask_from_image(mask_img)
|
| 162 |
+
|
| 163 |
+
result = process_inpaint(np.array(img_rgba), mask_rgba)
|
| 164 |
+
result_name = f"output_{uuid.uuid4().hex}.png"
|
| 165 |
+
result_path = os.path.join(OUTPUT_DIR, result_name)
|
| 166 |
+
Image.fromarray(result).save(result_path)
|
| 167 |
+
|
| 168 |
+
url = str(request.url_for("download_file", filename=result_name))
|
| 169 |
+
logs.append({"result": result_name, "url": url, "timestamp": datetime.utcnow().isoformat()})
|
| 170 |
+
return {"result": result_name, "url": url}
|
| 171 |
+
|
| 172 |
+
|
| 173 |
@app.post("/inpaint-multipart")
|
| 174 |
def inpaint_multipart(
|
| 175 |
image: UploadFile = File(...),
|
| 176 |
mask: UploadFile = File(...),
|
| 177 |
+
request: Request = None,
|
| 178 |
_: None = Depends(bearer_auth),
|
| 179 |
) -> Dict[str, str]:
|
| 180 |
# Load in-memory
|
|
|
|
| 187 |
result_path = os.path.join(OUTPUT_DIR, result_name)
|
| 188 |
Image.fromarray(result).save(result_path)
|
| 189 |
|
| 190 |
+
url: Optional[str] = None
|
| 191 |
+
try:
|
| 192 |
+
if request is not None:
|
| 193 |
+
url = str(request.url_for("download_file", filename=result_name))
|
| 194 |
+
except Exception:
|
| 195 |
+
url = None
|
| 196 |
+
|
| 197 |
+
entry: Dict[str, str] = {"result": result_name, "timestamp": datetime.utcnow().isoformat()}
|
| 198 |
+
if url:
|
| 199 |
+
entry["url"] = url
|
| 200 |
+
logs.append(entry)
|
| 201 |
+
resp: Dict[str, str] = {"result": result_name}
|
| 202 |
+
if url:
|
| 203 |
+
resp["url"] = url
|
| 204 |
+
return resp
|
| 205 |
|
| 206 |
|
| 207 |
@app.get("/download/{filename}")
|