Update app.py
Browse files
app.py
CHANGED
|
@@ -4,12 +4,15 @@
|
|
| 4 |
GPU-optimized with real verification & self-tests (no mocks).
|
| 5 |
|
| 6 |
Key guarantees:
|
| 7 |
-
- MatAnyone init
|
| 8 |
- SAM2 marked Verified only after a successful micro-inference.
|
| 9 |
- rembg verified by actually removing BG on a tiny image.
|
| 10 |
- FFmpeg/MoviePy tested by encoding/decoding a tiny clip.
|
| 11 |
- CUDA verified by real CUDA ops; GPU stats shown from PyTorch (NVML if present).
|
| 12 |
- "Run Self-Test" button & "--self-test" CLI flag for CI / manual checks.
|
|
|
|
|
|
|
|
|
|
| 13 |
"""
|
| 14 |
|
| 15 |
import os
|
|
@@ -55,9 +58,9 @@
|
|
| 55 |
os.environ["GRADIO_SERVER_PORT"] = "7860"
|
| 56 |
|
| 57 |
# Feature flags
|
| 58 |
-
os.environ["USE_MATANYONE"] = "true"
|
| 59 |
-
os.environ["USE_SAM2"] = "true"
|
| 60 |
-
os.environ["SELF_CHECK_MODE"] = "false"
|
| 61 |
|
| 62 |
# =========================
|
| 63 |
# Imports
|
|
@@ -315,18 +318,20 @@ def build_professional_bg(w, h, preset: str) -> np.ndarray:
|
|
| 315 |
return make_solid(w, h, (240, 240, 240))
|
| 316 |
|
| 317 |
# =========================
|
| 318 |
-
# MatAnyone wrapper (
|
| 319 |
# =========================
|
| 320 |
class OptimizedMatAnyoneProcessor:
|
| 321 |
"""
|
| 322 |
-
|
| 323 |
-
|
|
|
|
| 324 |
"""
|
| 325 |
def __init__(self):
|
| 326 |
self.processor = None
|
| 327 |
self.device = "cuda" if (TORCH_AVAILABLE and CUDA_AVAILABLE) else "cpu"
|
| 328 |
self.initialized = False
|
| 329 |
self.verified = False
|
|
|
|
| 330 |
|
| 331 |
def initialize(self) -> bool:
|
| 332 |
if not MATANYONE_IMPORTED:
|
|
@@ -334,18 +339,72 @@ def initialize(self) -> bool:
|
|
| 334 |
return False
|
| 335 |
if self.initialized and self.processor is not None:
|
| 336 |
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
try:
|
| 338 |
-
print(f"Initializing MatAnyone on {self.device}…")
|
| 339 |
-
self.processor = MatAnyInferenceCore("PeiqingYang/MatAnyone") #
|
| 340 |
self.verified = hasattr(self.processor, "process_video")
|
| 341 |
if self.device == "cuda":
|
| 342 |
torch.cuda.empty_cache()
|
| 343 |
-
_ = torch.rand(1, device="cuda") * 0.0 #
|
| 344 |
self.initialized = True
|
| 345 |
-
print("✅ MatAnyone initialized
|
| 346 |
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
except Exception as e:
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
import traceback; traceback.print_exc()
|
| 350 |
return False
|
| 351 |
|
|
@@ -374,7 +433,7 @@ def create_mask_optimized(self, video_path: str, output_path: str) -> str:
|
|
| 374 |
except Exception as e:
|
| 375 |
print(f"SAM2 mask creation failed; fallback rectangle. Error: {e}")
|
| 376 |
|
| 377 |
-
# Fallback: centered box
|
| 378 |
h, w = frame.shape[:2]
|
| 379 |
mask = np.zeros((h, w), dtype=np.uint8)
|
| 380 |
mx, my = int(w * 0.15), int(h * 0.10)
|
|
@@ -575,8 +634,9 @@ def self_test_sam2() -> (bool, str):
|
|
| 575 |
|
| 576 |
def self_test_matanyone() -> (bool, str):
|
| 577 |
try:
|
| 578 |
-
|
| 579 |
-
|
|
|
|
| 580 |
if not matanyone_processor.verified:
|
| 581 |
return False, "MatAnyone missing process_video API"
|
| 582 |
# Create a tiny real video + mask, then run process_video
|
|
@@ -588,8 +648,15 @@ def self_test_matanyone() -> (bool, str):
|
|
| 588 |
x = 8 + t*4
|
| 589 |
cv2.rectangle(frame, (x, 20), (x+12, 44), (200, 200, 200), -1)
|
| 590 |
frames.append(frame)
|
|
|
|
|
|
|
| 591 |
vid_path = os.path.join(td, "tiny_input.mp4")
|
| 592 |
-
ImageSequenceClip(frames, fps=8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
|
| 594 |
# Simple central mask (seed)
|
| 595 |
mask = np.zeros((64, 64), dtype=np.uint8)
|
|
@@ -703,6 +770,10 @@ def gradio_interface_optimized(video_file, bg_image, use_matanyone=True, bg_pres
|
|
| 703 |
def gradio_run_self_test():
|
| 704 |
return run_self_test()
|
| 705 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 706 |
# =========================
|
| 707 |
# UI
|
| 708 |
# =========================
|
|
@@ -756,6 +827,10 @@ def gradio_run_self_test():
|
|
| 756 |
selftest_btn = gr.Button("Run Self-Test")
|
| 757 |
selftest_out = gr.Textbox(label="Self-Test Report", lines=16)
|
| 758 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 759 |
with gr.Column():
|
| 760 |
output_video = gr.Video(label="✨ Result")
|
| 761 |
download_file = gr.File(label="💾 Download")
|
|
@@ -773,11 +848,18 @@ def gradio_run_self_test():
|
|
| 773 |
outputs=[selftest_out],
|
| 774 |
)
|
| 775 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 776 |
gr.Markdown("---")
|
| 777 |
gr.Markdown("""
|
| 778 |
**Notes**
|
| 779 |
- SAM2 shows ✅ only after a real micro-inference passes.
|
| 780 |
- MatAnyone shows ✅ only if initialization succeeded and `process_video` exists; the self-test also runs a tiny real video through it.
|
|
|
|
| 781 |
- FFmpeg/MoviePy, CUDA, and rembg are validated by actually running them.
|
| 782 |
""")
|
| 783 |
|
|
|
|
| 4 |
GPU-optimized with real verification & self-tests (no mocks).
|
| 5 |
|
| 6 |
Key guarantees:
|
| 7 |
+
- MatAnyone init tries HF repo-id first, then local checkpoint fallback; errors are surfaced.
|
| 8 |
- SAM2 marked Verified only after a successful micro-inference.
|
| 9 |
- rembg verified by actually removing BG on a tiny image.
|
| 10 |
- FFmpeg/MoviePy tested by encoding/decoding a tiny clip.
|
| 11 |
- CUDA verified by real CUDA ops; GPU stats shown from PyTorch (NVML if present).
|
| 12 |
- "Run Self-Test" button & "--self-test" CLI flag for CI / manual checks.
|
| 13 |
+
|
| 14 |
+
NOTE: Ensure requirements include at least:
|
| 15 |
+
huggingface-hub, requests, hydra-core, omegaconf, einops, timm, opencv-python-headless, moviepy, rembg
|
| 16 |
"""
|
| 17 |
|
| 18 |
import os
|
|
|
|
| 58 |
os.environ["GRADIO_SERVER_PORT"] = "7860"
|
| 59 |
|
| 60 |
# Feature flags
|
| 61 |
+
os.environ["USE_MATANYONE"] = os.getenv("USE_MATANYONE", "true")
|
| 62 |
+
os.environ["USE_SAM2"] = os.getenv("USE_SAM2", "true")
|
| 63 |
+
os.environ["SELF_CHECK_MODE"] = os.getenv("SELF_CHECK_MODE", "false")
|
| 64 |
|
| 65 |
# =========================
|
| 66 |
# Imports
|
|
|
|
| 318 |
return make_solid(w, h, (240, 240, 240))
|
| 319 |
|
| 320 |
# =========================
|
| 321 |
+
# MatAnyone wrapper (robust)
|
| 322 |
# =========================
|
| 323 |
class OptimizedMatAnyoneProcessor:
|
| 324 |
"""
|
| 325 |
+
Wrapper around MatAnyone's InferenceCore.
|
| 326 |
+
Tries HF repo-id first; if that fails (or the local class expects a `network`),
|
| 327 |
+
falls back to downloading `pretrained_models/matanyone.pth` and building the model.
|
| 328 |
"""
|
| 329 |
def __init__(self):
|
| 330 |
self.processor = None
|
| 331 |
self.device = "cuda" if (TORCH_AVAILABLE and CUDA_AVAILABLE) else "cpu"
|
| 332 |
self.initialized = False
|
| 333 |
self.verified = False
|
| 334 |
+
self.last_error = None # for diagnostics
|
| 335 |
|
| 336 |
def initialize(self) -> bool:
|
| 337 |
if not MATANYONE_IMPORTED:
|
|
|
|
| 339 |
return False
|
| 340 |
if self.initialized and self.processor is not None:
|
| 341 |
return True
|
| 342 |
+
|
| 343 |
+
self.last_error = None
|
| 344 |
+
|
| 345 |
+
# 1) Preferred path: HF repo-id (documented usage)
|
| 346 |
try:
|
| 347 |
+
print(f"Initializing MatAnyone (HF repo-id) on {self.device}…")
|
| 348 |
+
self.processor = MatAnyInferenceCore("PeiqingYang/MatAnyone") # per README
|
| 349 |
self.verified = hasattr(self.processor, "process_video")
|
| 350 |
if self.device == "cuda":
|
| 351 |
torch.cuda.empty_cache()
|
| 352 |
+
_ = torch.rand(1, device="cuda") * 0.0 # warmup
|
| 353 |
self.initialized = True
|
| 354 |
+
print("✅ MatAnyone initialized via HF repo-id.")
|
| 355 |
return True
|
| 356 |
+
except TypeError as e:
|
| 357 |
+
# Classic sign of older API: __init__ requires a `network` object
|
| 358 |
+
if "network" in str(e).lower():
|
| 359 |
+
print("MatAnyone InferenceCore expects a `network` (older API); will build network locally.")
|
| 360 |
+
else:
|
| 361 |
+
print(f"HF init TypeError: {e}")
|
| 362 |
+
self.last_error = f"HF init TypeError: {e}"
|
| 363 |
except Exception as e:
|
| 364 |
+
self.last_error = f"HF init failed: {type(e).__name__}: {e}"
|
| 365 |
+
print(self.last_error)
|
| 366 |
+
|
| 367 |
+
# 2) Fallback: local checkpoint → network object → InferenceCore(network)
|
| 368 |
+
try:
|
| 369 |
+
print("Falling back to local checkpoint init for MatAnyone…")
|
| 370 |
+
from pathlib import Path
|
| 371 |
+
import requests
|
| 372 |
+
from matanyone.utils.get_default_model import get_matanyone_model
|
| 373 |
+
|
| 374 |
+
ckpt_dir = Path("./pretrained_models")
|
| 375 |
+
ckpt_dir.mkdir(parents=True, exist_ok=True)
|
| 376 |
+
ckpt_path = ckpt_dir / "matanyone.pth"
|
| 377 |
+
|
| 378 |
+
if not ckpt_path.exists():
|
| 379 |
+
url = "https://github.com/pq-yang/MatAnyone/releases/download/v1.0.0/matanyone.pth"
|
| 380 |
+
print(f"Downloading MatAnyone checkpoint from: {url}")
|
| 381 |
+
with requests.get(url, stream=True, timeout=180) as r:
|
| 382 |
+
r.raise_for_status()
|
| 383 |
+
with open(ckpt_path, "wb") as f:
|
| 384 |
+
for chunk in r.iter_content(chunk_size=8192):
|
| 385 |
+
if chunk:
|
| 386 |
+
f.write(chunk)
|
| 387 |
+
print(f"Checkpoint saved to {ckpt_path}")
|
| 388 |
+
|
| 389 |
+
# Build the actual network then wrap with InferenceCore
|
| 390 |
+
network = get_matanyone_model(
|
| 391 |
+
str(ckpt_path),
|
| 392 |
+
device=("cuda" if CUDA_AVAILABLE else "cpu")
|
| 393 |
+
)
|
| 394 |
+
self.processor = MatAnyInferenceCore(network)
|
| 395 |
+
self.verified = hasattr(self.processor, "process_video")
|
| 396 |
+
|
| 397 |
+
if self.device == "cuda":
|
| 398 |
+
torch.cuda.empty_cache()
|
| 399 |
+
_ = torch.rand(1, device="cuda") * 0.0
|
| 400 |
+
|
| 401 |
+
self.initialized = True
|
| 402 |
+
print("✅ MatAnyone initialized via local checkpoint.")
|
| 403 |
+
return True
|
| 404 |
+
|
| 405 |
+
except Exception as e:
|
| 406 |
+
self.last_error = f"Local init failed: {type(e).__name__}: {e}"
|
| 407 |
+
print(f"MatAnyone initialization failed: {self.last_error}")
|
| 408 |
import traceback; traceback.print_exc()
|
| 409 |
return False
|
| 410 |
|
|
|
|
| 433 |
except Exception as e:
|
| 434 |
print(f"SAM2 mask creation failed; fallback rectangle. Error: {e}")
|
| 435 |
|
| 436 |
+
# Fallback: centered box
|
| 437 |
h, w = frame.shape[:2]
|
| 438 |
mask = np.zeros((h, w), dtype=np.uint8)
|
| 439 |
mx, my = int(w * 0.15), int(h * 0.10)
|
|
|
|
| 634 |
|
| 635 |
def self_test_matanyone() -> (bool, str):
|
| 636 |
try:
|
| 637 |
+
ok_init = matanyone_processor.initialize()
|
| 638 |
+
if not ok_init:
|
| 639 |
+
return False, f"MatAnyone init failed: {getattr(matanyone_processor, 'last_error', 'no details')}"
|
| 640 |
if not matanyone_processor.verified:
|
| 641 |
return False, "MatAnyone missing process_video API"
|
| 642 |
# Create a tiny real video + mask, then run process_video
|
|
|
|
| 648 |
x = 8 + t*4
|
| 649 |
cv2.rectangle(frame, (x, 20), (x+12, 44), (200, 200, 200), -1)
|
| 650 |
frames.append(frame)
|
| 651 |
+
|
| 652 |
+
# Write temp video and a visible copy you can inspect
|
| 653 |
vid_path = os.path.join(td, "tiny_input.mp4")
|
| 654 |
+
clip = ImageSequenceClip(frames, fps=8)
|
| 655 |
+
clip.write_videofile(vid_path, audio=False, logger=None)
|
| 656 |
+
visible_test_clip = CHECKPOINTS_DIR / "selftest_clip.mp4"
|
| 657 |
+
clip.write_videofile(str(visible_test_clip), audio=False, logger=None)
|
| 658 |
+
clip.close()
|
| 659 |
+
print(f"📹 Self-test clip saved to {visible_test_clip}")
|
| 660 |
|
| 661 |
# Simple central mask (seed)
|
| 662 |
mask = np.zeros((64, 64), dtype=np.uint8)
|
|
|
|
| 770 |
def gradio_run_self_test():
|
| 771 |
return run_self_test()
|
| 772 |
|
| 773 |
+
def show_matanyone_diag():
|
| 774 |
+
ok = matanyone_processor.initialized and matanyone_processor.verified
|
| 775 |
+
return "READY ✅" if ok else (matanyone_processor.last_error or "Not initialized yet")
|
| 776 |
+
|
| 777 |
# =========================
|
| 778 |
# UI
|
| 779 |
# =========================
|
|
|
|
| 827 |
selftest_btn = gr.Button("Run Self-Test")
|
| 828 |
selftest_out = gr.Textbox(label="Self-Test Report", lines=16)
|
| 829 |
|
| 830 |
+
gr.Markdown("### 🛠 MatAnyone Diagnostics")
|
| 831 |
+
mat_diag_btn = gr.Button("Show MatAnyone Diagnostics")
|
| 832 |
+
mat_diag_out = gr.Textbox(label="MatAnyone Last Error / Status", lines=6)
|
| 833 |
+
|
| 834 |
with gr.Column():
|
| 835 |
output_video = gr.Video(label="✨ Result")
|
| 836 |
download_file = gr.File(label="💾 Download")
|
|
|
|
| 848 |
outputs=[selftest_out],
|
| 849 |
)
|
| 850 |
|
| 851 |
+
mat_diag_btn.click(
|
| 852 |
+
fn=show_matanyone_diag,
|
| 853 |
+
inputs=[],
|
| 854 |
+
outputs=[mat_diag_out],
|
| 855 |
+
)
|
| 856 |
+
|
| 857 |
gr.Markdown("---")
|
| 858 |
gr.Markdown("""
|
| 859 |
**Notes**
|
| 860 |
- SAM2 shows ✅ only after a real micro-inference passes.
|
| 861 |
- MatAnyone shows ✅ only if initialization succeeded and `process_video` exists; the self-test also runs a tiny real video through it.
|
| 862 |
+
- The self-test saves a visible clip to `checkpoints/selftest_clip.mp4` so you can inspect what was used.
|
| 863 |
- FFmpeg/MoviePy, CUDA, and rembg are validated by actually running them.
|
| 864 |
""")
|
| 865 |
|