Update app.py
Browse files
app.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
-
# app.py - Background Remover v32.2.
|
| 3 |
-
# -
|
| 4 |
-
# -
|
| 5 |
-
# -
|
| 6 |
-
# -
|
| 7 |
-
# - Bulk: original filename preserved, 50-cap server-side
|
| 8 |
|
| 9 |
import io
|
| 10 |
import time
|
|
@@ -19,7 +18,6 @@ import gc
|
|
| 19 |
import shutil
|
| 20 |
import zipfile
|
| 21 |
import json
|
| 22 |
-
import resource
|
| 23 |
import uuid
|
| 24 |
import base64
|
| 25 |
import urllib.parse
|
|
@@ -33,9 +31,6 @@ import psutil
|
|
| 33 |
|
| 34 |
# ============================================================
|
| 35 |
# ONNX RUNTIME
|
| 36 |
-
# Arena OFF. BFC fragmented on BiRefNet-lite's deformable ASPP
|
| 37 |
-
# and produced 822MB single-buffer allocation failures at 896px.
|
| 38 |
-
# Per-inference memory is now predictable at the cost of ~10% speed.
|
| 39 |
# ============================================================
|
| 40 |
try:
|
| 41 |
import onnxruntime as _ort
|
|
@@ -422,16 +417,8 @@ class ContentClassifier:
|
|
| 422 |
return 'complex'
|
| 423 |
|
| 424 |
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
soft, hard = resource.getrlimit(resource.RLIMIT_AS)
|
| 428 |
-
resource.setrlimit(resource.RLIMIT_AS, (12 * 1024 * 1024 * 1024, hard))
|
| 429 |
-
logger.info("Memory limit set to 12GB")
|
| 430 |
-
except Exception as e:
|
| 431 |
-
logger.warning(f"Could not set memory limit: {e}")
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
limit_memory()
|
| 435 |
|
| 436 |
os.environ["U2NET_HOME"] = "/tmp/u2net_models"
|
| 437 |
os.environ["OMP_NUM_THREADS"] = "2"
|
|
@@ -860,7 +847,14 @@ def _prewarm_models():
|
|
| 860 |
try:
|
| 861 |
test_img = Image.new('RGB', (64, 64), (255, 255, 255))
|
| 862 |
ImageDraw.Draw(test_img).rectangle([20, 20, 44, 44], fill=(220, 40, 40))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 863 |
for name, _ in PRELOAD_MODELS:
|
|
|
|
|
|
|
|
|
|
| 864 |
session = model_manager.models.get(name)
|
| 865 |
if session is None:
|
| 866 |
continue
|
|
@@ -879,12 +873,13 @@ def _prewarm_models():
|
|
| 879 |
logger.warning(f" {name} WRONG (center={ca} corner={corner})")
|
| 880 |
except Exception as e:
|
| 881 |
logger.warning(f" {name}: {type(e).__name__}: {e}")
|
|
|
|
| 882 |
except Exception as e:
|
| 883 |
logger.warning(f"Pre-warm skipped: {e}")
|
| 884 |
|
| 885 |
|
| 886 |
logger.info("=" * 60)
|
| 887 |
-
logger.info("Background Remover v32.2.
|
| 888 |
logger.info("=" * 60)
|
| 889 |
pre_download_models()
|
| 890 |
initialize_models()
|
|
@@ -1019,8 +1014,6 @@ def analyze_image_complexity(image):
|
|
| 1019 |
and cc_edge > 0.04
|
| 1020 |
)
|
| 1021 |
|
| 1022 |
-
# Vehicle path prefers BiRefNet-lite for quality on glossy surfaces.
|
| 1023 |
-
# Falls back to Silueta if BiRefNet-lite isn't loaded.
|
| 1024 |
if is_vehicle_like:
|
| 1025 |
if has_birefnet:
|
| 1026 |
return _route(
|
|
@@ -1259,7 +1252,7 @@ def process_image_sync(image_data, model_name=None, transparent=True,
|
|
| 1259 |
|
| 1260 |
|
| 1261 |
# ============================================================
|
| 1262 |
-
# VIDEO FRAME PIPELINE
|
| 1263 |
# ============================================================
|
| 1264 |
def _process_frames_sync(raw_frames, resolved_model, transparent,
|
| 1265 |
session_id: Optional[str] = None,
|
|
@@ -1362,7 +1355,7 @@ def _process_frames_sync(raw_frames, resolved_model, transparent,
|
|
| 1362 |
# ============================================================
|
| 1363 |
# FASTAPI
|
| 1364 |
# ============================================================
|
| 1365 |
-
app = FastAPI(title="Background Remover", version="32.2.
|
| 1366 |
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
|
| 1367 |
allow_methods=["*"], allow_headers=["*"])
|
| 1368 |
|
|
@@ -1570,9 +1563,6 @@ async def process_bulk_images(
|
|
| 1570 |
raise HTTPException(500, str(e)[:200])
|
| 1571 |
|
| 1572 |
|
| 1573 |
-
# ============================================================
|
| 1574 |
-
# FRAMES BATCH — non-blocking + hybrid temporal smoothing
|
| 1575 |
-
# ============================================================
|
| 1576 |
@app.post("/api/process/frames-batch")
|
| 1577 |
async def process_frames_batch(
|
| 1578 |
files: List[UploadFile] = File(...),
|
|
@@ -1734,6 +1724,7 @@ async def debug_env():
|
|
| 1734 |
'matting_models': sorted(MATTING_MODELS),
|
| 1735 |
'vehicle_res_cap': VEHICLE_RES_CAP,
|
| 1736 |
'frame_max_dim': FRAME_MAX_DIM,
|
|
|
|
| 1737 |
'alpha_smoothing': _alpha_cache_stats()}
|
| 1738 |
|
| 1739 |
|
|
@@ -1744,7 +1735,7 @@ async def home():
|
|
| 1744 |
smoothing = _alpha_cache_stats()
|
| 1745 |
return HTMLResponse(f"""
|
| 1746 |
<!DOCTYPE html><html><head><meta charset="UTF-8">
|
| 1747 |
-
<title>Background Remover v32.2.
|
| 1748 |
<style>body{{font-family:system-ui;background:#0A0A0A;
|
| 1749 |
color:#fff;min-height:100vh;display:flex;align-items:center;justify-content:center;
|
| 1750 |
margin:0;padding:20px}}.c{{max-width:900px;background:#111;
|
|
@@ -1752,13 +1743,13 @@ async def home():
|
|
| 1752 |
border:1px solid #222}}.s{{padding:15px;border-radius:12px;
|
| 1753 |
margin:20px 0;background:#1a1a1a}}a{{color:#7ED957}}</style>
|
| 1754 |
</head><body><div class="c">
|
| 1755 |
-
<h1>Background Remover v32.2.
|
| 1756 |
<div class="s">AI online. {len(stats['loaded_models'])} models.
|
| 1757 |
RAM {mem['percent']:.0f}% ({mem['used_mb']}/{mem['total_mb']}MB)</div>
|
| 1758 |
<p>Default: <strong>ISNet</strong> · Vehicles/F1: <strong>BiRefNet-lite @ {VEHICLE_RES_CAP}px</strong></p>
|
| 1759 |
<p>Video: <strong>{DEFAULT_VIDEO_MODEL}</strong> @ {FRAME_MAX_DIM}px · alpha smooth {smoothing['smooth_weight']}</p>
|
| 1760 |
<p>Sessions cached: {smoothing['sessions']}/{smoothing['max_sessions']}</p>
|
| 1761 |
-
<p>ONNX: arena=OFF</p>
|
| 1762 |
<p><a href="/docs">API Docs</a></p>
|
| 1763 |
</div></body></html>
|
| 1764 |
""")
|
|
@@ -1781,7 +1772,7 @@ async def health_check():
|
|
| 1781 |
'pymatting_available': PYMATTING_AVAILABLE,
|
| 1782 |
'image_formats': ['png', 'jpg', 'jpeg', 'webp', 'bmp'],
|
| 1783 |
'bulk_limit': 50,
|
| 1784 |
-
'version': '32.2.
|
| 1785 |
'default_model': get_default_model(),
|
| 1786 |
'default_video_model': DEFAULT_VIDEO_MODEL,
|
| 1787 |
'preload_models': [n for n, _ in PRELOAD_MODELS],
|
|
@@ -1791,6 +1782,7 @@ async def health_check():
|
|
| 1791 |
'vehicle_res_cap': VEHICLE_RES_CAP,
|
| 1792 |
'frame_max_dim': FRAME_MAX_DIM,
|
| 1793 |
'onnx_arena': False,
|
|
|
|
| 1794 |
'alpha_smoothing': _alpha_cache_stats(),
|
| 1795 |
'frame_batch_endpoint': '/api/process/frames-batch',
|
| 1796 |
'frame_batch_size_max': 4,
|
|
@@ -1818,11 +1810,10 @@ async def gen_exc(request, exc):
|
|
| 1818 |
@app.on_event("startup")
|
| 1819 |
async def startup():
|
| 1820 |
logger.info("=" * 60)
|
| 1821 |
-
logger.info("v32.2.
|
| 1822 |
logger.info("ONNX: arena=OFF, mem_pattern=OFF")
|
|
|
|
| 1823 |
logger.info("Video: Silueta default + temporal alpha smoothing")
|
| 1824 |
-
logger.info(f"Smoothing weight: {ALPHA_SMOOTH_WEIGHT}, "
|
| 1825 |
-
f"max sessions: {ALPHA_CACHE_MAX_SESSIONS}")
|
| 1826 |
logger.info(f"Video default model: {DEFAULT_VIDEO_MODEL}")
|
| 1827 |
logger.info(f"Video max frame dim: {FRAME_MAX_DIM}")
|
| 1828 |
logger.info(f"Vehicle res cap: {VEHICLE_RES_CAP}")
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
+
# app.py - Background Remover v32.2.4
|
| 3 |
+
# - RLIMIT_AS removed (was causing spurious bad_alloc at 12GB VA)
|
| 4 |
+
# - BiRefNet-lite warmup skipped (was inflating RSS by ~6.8GB)
|
| 5 |
+
# - Silueta default for video; proxy forces it
|
| 6 |
+
# - ONNX arena OFF, VEHICLE_RES_CAP 640, FRAME_MAX_DIM 640
|
|
|
|
| 7 |
|
| 8 |
import io
|
| 9 |
import time
|
|
|
|
| 18 |
import shutil
|
| 19 |
import zipfile
|
| 20 |
import json
|
|
|
|
| 21 |
import uuid
|
| 22 |
import base64
|
| 23 |
import urllib.parse
|
|
|
|
| 31 |
|
| 32 |
# ============================================================
|
| 33 |
# ONNX RUNTIME
|
|
|
|
|
|
|
|
|
|
| 34 |
# ============================================================
|
| 35 |
try:
|
| 36 |
import onnxruntime as _ort
|
|
|
|
| 417 |
return 'complex'
|
| 418 |
|
| 419 |
|
| 420 |
+
# RLIMIT_AS removed — see note above.
|
| 421 |
+
logger.info("RLIMIT_AS not set — relying on cgroup memory.max")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
|
| 423 |
os.environ["U2NET_HOME"] = "/tmp/u2net_models"
|
| 424 |
os.environ["OMP_NUM_THREADS"] = "2"
|
|
|
|
| 847 |
try:
|
| 848 |
test_img = Image.new('RGB', (64, 64), (255, 255, 255))
|
| 849 |
ImageDraw.Draw(test_img).rectangle([20, 20, 44, 44], fill=(220, 40, 40))
|
| 850 |
+
# BiRefNet-lite warmup forces a 1024x1024 internal inference that
|
| 851 |
+
# reserves ~6 GB of transient allocator state which libc does not
|
| 852 |
+
# return to the OS on CPU-only boxes. Skip it.
|
| 853 |
+
WARMUP_SKIP = {'birefnet-general-lite'}
|
| 854 |
for name, _ in PRELOAD_MODELS:
|
| 855 |
+
if name in WARMUP_SKIP:
|
| 856 |
+
logger.info(f" {name} skipped (heavy warmup)")
|
| 857 |
+
continue
|
| 858 |
session = model_manager.models.get(name)
|
| 859 |
if session is None:
|
| 860 |
continue
|
|
|
|
| 873 |
logger.warning(f" {name} WRONG (center={ca} corner={corner})")
|
| 874 |
except Exception as e:
|
| 875 |
logger.warning(f" {name}: {type(e).__name__}: {e}")
|
| 876 |
+
gc.collect()
|
| 877 |
except Exception as e:
|
| 878 |
logger.warning(f"Pre-warm skipped: {e}")
|
| 879 |
|
| 880 |
|
| 881 |
logger.info("=" * 60)
|
| 882 |
+
logger.info("Background Remover v32.2.4")
|
| 883 |
logger.info("=" * 60)
|
| 884 |
pre_download_models()
|
| 885 |
initialize_models()
|
|
|
|
| 1014 |
and cc_edge > 0.04
|
| 1015 |
)
|
| 1016 |
|
|
|
|
|
|
|
| 1017 |
if is_vehicle_like:
|
| 1018 |
if has_birefnet:
|
| 1019 |
return _route(
|
|
|
|
| 1252 |
|
| 1253 |
|
| 1254 |
# ============================================================
|
| 1255 |
+
# VIDEO FRAME PIPELINE
|
| 1256 |
# ============================================================
|
| 1257 |
def _process_frames_sync(raw_frames, resolved_model, transparent,
|
| 1258 |
session_id: Optional[str] = None,
|
|
|
|
| 1355 |
# ============================================================
|
| 1356 |
# FASTAPI
|
| 1357 |
# ============================================================
|
| 1358 |
+
app = FastAPI(title="Background Remover", version="32.2.4")
|
| 1359 |
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
|
| 1360 |
allow_methods=["*"], allow_headers=["*"])
|
| 1361 |
|
|
|
|
| 1563 |
raise HTTPException(500, str(e)[:200])
|
| 1564 |
|
| 1565 |
|
|
|
|
|
|
|
|
|
|
| 1566 |
@app.post("/api/process/frames-batch")
|
| 1567 |
async def process_frames_batch(
|
| 1568 |
files: List[UploadFile] = File(...),
|
|
|
|
| 1724 |
'matting_models': sorted(MATTING_MODELS),
|
| 1725 |
'vehicle_res_cap': VEHICLE_RES_CAP,
|
| 1726 |
'frame_max_dim': FRAME_MAX_DIM,
|
| 1727 |
+
'rlimit_as': None,
|
| 1728 |
'alpha_smoothing': _alpha_cache_stats()}
|
| 1729 |
|
| 1730 |
|
|
|
|
| 1735 |
smoothing = _alpha_cache_stats()
|
| 1736 |
return HTMLResponse(f"""
|
| 1737 |
<!DOCTYPE html><html><head><meta charset="UTF-8">
|
| 1738 |
+
<title>Background Remover v32.2.4</title>
|
| 1739 |
<style>body{{font-family:system-ui;background:#0A0A0A;
|
| 1740 |
color:#fff;min-height:100vh;display:flex;align-items:center;justify-content:center;
|
| 1741 |
margin:0;padding:20px}}.c{{max-width:900px;background:#111;
|
|
|
|
| 1743 |
border:1px solid #222}}.s{{padding:15px;border-radius:12px;
|
| 1744 |
margin:20px 0;background:#1a1a1a}}a{{color:#7ED957}}</style>
|
| 1745 |
</head><body><div class="c">
|
| 1746 |
+
<h1>Background Remover v32.2.4</h1>
|
| 1747 |
<div class="s">AI online. {len(stats['loaded_models'])} models.
|
| 1748 |
RAM {mem['percent']:.0f}% ({mem['used_mb']}/{mem['total_mb']}MB)</div>
|
| 1749 |
<p>Default: <strong>ISNet</strong> · Vehicles/F1: <strong>BiRefNet-lite @ {VEHICLE_RES_CAP}px</strong></p>
|
| 1750 |
<p>Video: <strong>{DEFAULT_VIDEO_MODEL}</strong> @ {FRAME_MAX_DIM}px · alpha smooth {smoothing['smooth_weight']}</p>
|
| 1751 |
<p>Sessions cached: {smoothing['sessions']}/{smoothing['max_sessions']}</p>
|
| 1752 |
+
<p>ONNX: arena=OFF · RLIMIT_AS: unset</p>
|
| 1753 |
<p><a href="/docs">API Docs</a></p>
|
| 1754 |
</div></body></html>
|
| 1755 |
""")
|
|
|
|
| 1772 |
'pymatting_available': PYMATTING_AVAILABLE,
|
| 1773 |
'image_formats': ['png', 'jpg', 'jpeg', 'webp', 'bmp'],
|
| 1774 |
'bulk_limit': 50,
|
| 1775 |
+
'version': '32.2.4',
|
| 1776 |
'default_model': get_default_model(),
|
| 1777 |
'default_video_model': DEFAULT_VIDEO_MODEL,
|
| 1778 |
'preload_models': [n for n, _ in PRELOAD_MODELS],
|
|
|
|
| 1782 |
'vehicle_res_cap': VEHICLE_RES_CAP,
|
| 1783 |
'frame_max_dim': FRAME_MAX_DIM,
|
| 1784 |
'onnx_arena': False,
|
| 1785 |
+
'rlimit_as': None,
|
| 1786 |
'alpha_smoothing': _alpha_cache_stats(),
|
| 1787 |
'frame_batch_endpoint': '/api/process/frames-batch',
|
| 1788 |
'frame_batch_size_max': 4,
|
|
|
|
| 1810 |
@app.on_event("startup")
|
| 1811 |
async def startup():
|
| 1812 |
logger.info("=" * 60)
|
| 1813 |
+
logger.info("v32.2.4 ready")
|
| 1814 |
logger.info("ONNX: arena=OFF, mem_pattern=OFF")
|
| 1815 |
+
logger.info("RLIMIT_AS: unset (relying on cgroup memory.max)")
|
| 1816 |
logger.info("Video: Silueta default + temporal alpha smoothing")
|
|
|
|
|
|
|
| 1817 |
logger.info(f"Video default model: {DEFAULT_VIDEO_MODEL}")
|
| 1818 |
logger.info(f"Video max frame dim: {FRAME_MAX_DIM}")
|
| 1819 |
logger.info(f"Vehicle res cap: {VEHICLE_RES_CAP}")
|