Commit ·
4b22c57
1
Parent(s): 15a1a9a
Update app logic and deploy to Hugging Face
Browse files- app/main.py +43 -21
app/main.py
CHANGED
|
@@ -4,9 +4,11 @@ import tempfile
|
|
| 4 |
import subprocess
|
| 5 |
import cv2 # type: ignore
|
| 6 |
import numpy as np
|
| 7 |
-
|
|
|
|
| 8 |
from fastapi.responses import FileResponse
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
| 10 |
from rembg import new_session, remove
|
| 11 |
from enum import Enum
|
| 12 |
|
|
@@ -29,6 +31,11 @@ class ModelName(str, Enum):
|
|
| 29 |
# Cache sessions to avoid reloading models on every request
|
| 30 |
sessions = {}
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
def get_session(model_name: str):
|
| 33 |
if model_name not in sessions:
|
| 34 |
print(f"Loading model: {model_name}...")
|
|
@@ -46,7 +53,7 @@ async def startup_event():
|
|
| 46 |
|
| 47 |
@app.get("/")
|
| 48 |
def read_root():
|
| 49 |
-
return {"message": "Background Removal API is running"}
|
| 50 |
|
| 51 |
@app.post("/image-bg-removal")
|
| 52 |
async def image_bg_removal(
|
|
@@ -61,26 +68,35 @@ async def image_bg_removal(
|
|
| 61 |
Removes background from an image.
|
| 62 |
Returns the image with transparent background (PNG).
|
| 63 |
"""
|
|
|
|
| 64 |
input_image = await file.read()
|
| 65 |
|
| 66 |
session = get_session(model.value)
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
return Response(content=output_image, media_type="image/png")
|
| 86 |
|
|
@@ -94,14 +110,20 @@ async def video_bg_removal(
|
|
| 94 |
Removes background from a video.
|
| 95 |
Returns WebM with Alpha.
|
| 96 |
"""
|
| 97 |
-
# Create temp file for input
|
| 98 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_input:
|
| 99 |
shutil.copyfileobj(file.file, tmp_input)
|
| 100 |
tmp_input_path = tmp_input.name
|
| 101 |
|
| 102 |
try:
|
| 103 |
-
#
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
except Exception as e:
|
| 106 |
if os.path.exists(tmp_input_path):
|
| 107 |
os.remove(tmp_input_path)
|
|
|
|
| 4 |
import subprocess
|
| 5 |
import cv2 # type: ignore
|
| 6 |
import numpy as np
|
| 7 |
+
import asyncio
|
| 8 |
+
from fastapi import FastAPI, UploadFile, File, Response, BackgroundTasks, Query, HTTPException
|
| 9 |
from fastapi.responses import FileResponse
|
| 10 |
from fastapi.middleware.cors import CORSMiddleware
|
| 11 |
+
from fastapi.concurrency import run_in_threadpool
|
| 12 |
from rembg import new_session, remove
|
| 13 |
from enum import Enum
|
| 14 |
|
|
|
|
| 31 |
# Cache sessions to avoid reloading models on every request
|
| 32 |
sessions = {}
|
| 33 |
|
| 34 |
+
# Global semaphore to limit concurrent processing.
|
| 35 |
+
# Free tiers have limited CPU/RAM. We limit to 1 concurrent heavy task to prevent OOM/Crashes.
|
| 36 |
+
MAX_CONCURRENT_PROCESSING = 1
|
| 37 |
+
processing_semaphore = asyncio.Semaphore(MAX_CONCURRENT_PROCESSING)
|
| 38 |
+
|
| 39 |
def get_session(model_name: str):
|
| 40 |
if model_name not in sessions:
|
| 41 |
print(f"Loading model: {model_name}...")
|
|
|
|
| 53 |
|
| 54 |
@app.get("/")
|
| 55 |
def read_root():
|
| 56 |
+
return {"message": "Background Removal API is running", "concurrent_limit": MAX_CONCURRENT_PROCESSING}
|
| 57 |
|
| 58 |
@app.post("/image-bg-removal")
|
| 59 |
async def image_bg_removal(
|
|
|
|
| 68 |
Removes background from an image.
|
| 69 |
Returns the image with transparent background (PNG).
|
| 70 |
"""
|
| 71 |
+
# Read file content first (IO bound, doesn't need semaphore)
|
| 72 |
input_image = await file.read()
|
| 73 |
|
| 74 |
session = get_session(model.value)
|
| 75 |
|
| 76 |
+
# Acquire semaphore before heavy processing
|
| 77 |
+
if processing_semaphore.locked():
|
| 78 |
+
print("Waiting for processing slot...")
|
| 79 |
+
|
| 80 |
+
async with processing_semaphore:
|
| 81 |
+
try:
|
| 82 |
+
# Run blocking 'remove' function in a separate thread to avoid blocking the event loop
|
| 83 |
+
output_image = await run_in_threadpool(
|
| 84 |
+
remove,
|
| 85 |
+
input_image,
|
| 86 |
+
session=session,
|
| 87 |
+
alpha_matting=alpha_matting,
|
| 88 |
+
alpha_matting_foreground_threshold=alpha_matting_foreground_threshold,
|
| 89 |
+
alpha_matting_background_threshold=alpha_matting_background_threshold,
|
| 90 |
+
alpha_matting_erode_size=alpha_matting_erode_size
|
| 91 |
+
)
|
| 92 |
+
except Exception as e:
|
| 93 |
+
print(f"Error with alpha matting: {e}")
|
| 94 |
+
if alpha_matting:
|
| 95 |
+
print("Falling back to standard background removal (alpha_matting=False)...")
|
| 96 |
+
# Fallback also runs in thread pool
|
| 97 |
+
output_image = await run_in_threadpool(remove, input_image, session=session, alpha_matting=False)
|
| 98 |
+
else:
|
| 99 |
+
raise e
|
| 100 |
|
| 101 |
return Response(content=output_image, media_type="image/png")
|
| 102 |
|
|
|
|
| 110 |
Removes background from a video.
|
| 111 |
Returns WebM with Alpha.
|
| 112 |
"""
|
| 113 |
+
# Create temp file for input (IO bound)
|
| 114 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_input:
|
| 115 |
shutil.copyfileobj(file.file, tmp_input)
|
| 116 |
tmp_input_path = tmp_input.name
|
| 117 |
|
| 118 |
try:
|
| 119 |
+
# Acquire semaphore for the heavy video processing
|
| 120 |
+
if processing_semaphore.locked():
|
| 121 |
+
print("Waiting for video processing slot...")
|
| 122 |
+
|
| 123 |
+
async with processing_semaphore:
|
| 124 |
+
# Pass model name to processing function, run in thread pool
|
| 125 |
+
output_path = await run_in_threadpool(process_video, tmp_input_path, model.value)
|
| 126 |
+
|
| 127 |
except Exception as e:
|
| 128 |
if os.path.exists(tmp_input_path):
|
| 129 |
os.remove(tmp_input_path)
|