Spaces:
Running on Zero
Running on Zero
Add generate_from_image_video: in-Space SAM3 masking (SCAIL-Pose e2e) + ultralytics/moviepy
#1
by pormungtai - opened
- app.py +129 -0
- requirements.txt +3 -0
app.py
CHANGED
|
@@ -1255,6 +1255,96 @@ def _sampling_controls(prefix: str = ""):
|
|
| 1255 |
return steps, cfg, shift, seed, target_size, segment_len, segment_overlap
|
| 1256 |
|
| 1257 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1258 |
def build_ui():
|
| 1259 |
examples = _existing_examples()
|
| 1260 |
example_names = list(examples.keys())
|
|
@@ -1371,6 +1461,41 @@ def build_ui():
|
|
| 1371 |
outputs=[upload_output, upload_status],
|
| 1372 |
)
|
| 1373 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1374 |
with gr.Tab("Advanced Pack"):
|
| 1375 |
gr.Markdown(
|
| 1376 |
"Upload a `.zip` pack for multi-reference or multi-character inputs. "
|
|
@@ -1491,6 +1616,10 @@ if __name__ == "__main__":
|
|
| 1491 |
_prepare_runtime_for_startup()
|
| 1492 |
if PRELOAD_PIPELINE:
|
| 1493 |
_prepare_pipeline_for_startup()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1494 |
build_ui().queue(max_size=8).launch(
|
| 1495 |
allowed_paths=[str(OUTPUT_DIR.resolve())],
|
| 1496 |
show_error=True,
|
|
|
|
| 1255 |
return steps, cfg, shift, seed, target_size, segment_len, segment_overlap
|
| 1256 |
|
| 1257 |
|
| 1258 |
+
# ============================================================================
|
| 1259 |
+
# NEW: generate_from_image_video — ทำ SAM3 mask บน GPU เอง (ส่งแค่รูป+วิดีโอ)
|
| 1260 |
+
# ใช้ SCAIL-Pose e2e (process_one e2e_mode → SAM3 ผ่าน ultralytics) → _run_scail_job
|
| 1261 |
+
# ============================================================================
|
| 1262 |
+
import tempfile as _tempfile
|
| 1263 |
+
|
| 1264 |
+
_SCAIL_POSE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "SCAIL-Pose")
|
| 1265 |
+
if _SCAIL_POSE_DIR not in sys.path:
|
| 1266 |
+
sys.path.insert(0, _SCAIL_POSE_DIR)
|
| 1267 |
+
|
| 1268 |
+
SAM3_MODEL_PATH = os.getenv(
|
| 1269 |
+
"SAM3_MODEL", os.path.join(_SCAIL_POSE_DIR, "pretrained_weights", "sam3.pt")
|
| 1270 |
+
)
|
| 1271 |
+
|
| 1272 |
+
_sam3_predictor = None
|
| 1273 |
+
|
| 1274 |
+
|
| 1275 |
+
def _get_sam3_predictor():
|
| 1276 |
+
global _sam3_predictor
|
| 1277 |
+
if _sam3_predictor is None:
|
| 1278 |
+
from ultralytics.models.sam import SAM3VideoSemanticPredictor
|
| 1279 |
+
overrides = dict(
|
| 1280 |
+
conf=0.25, task="segment", mode="predict", imgsz=640,
|
| 1281 |
+
model=SAM3_MODEL_PATH, half=True, save=False, verbose=False,
|
| 1282 |
+
)
|
| 1283 |
+
_sam3_predictor = SAM3VideoSemanticPredictor(overrides=overrides, new_det_thresh=1.0)
|
| 1284 |
+
return _sam3_predictor
|
| 1285 |
+
|
| 1286 |
+
|
| 1287 |
+
def _make_masks_e2e(subdir, video_name="driving.mp4",
|
| 1288 |
+
text=("human", "character"), max_persons=1):
|
| 1289 |
+
from NLFPoseExtract.process_animation_aio import process_one
|
| 1290 |
+
predictor = _get_sam3_predictor()
|
| 1291 |
+
process_one(
|
| 1292 |
+
subdir, video_name, e2e_mode=True, crop_kind=None,
|
| 1293 |
+
max_persons=int(max_persons), text=list(text),
|
| 1294 |
+
model_nlf=None, detector=None, predictor=predictor, image_predictor=None,
|
| 1295 |
+
)
|
| 1296 |
+
return (
|
| 1297 |
+
os.path.join(subdir, "ref_mask.jpg"),
|
| 1298 |
+
os.path.join(subdir, "rendered_mask_v2.mp4"),
|
| 1299 |
+
os.path.join(subdir, "rendered_v2.mp4"),
|
| 1300 |
+
)
|
| 1301 |
+
|
| 1302 |
+
|
| 1303 |
+
def _ensure_sam3():
|
| 1304 |
+
if os.path.exists(SAM3_MODEL_PATH):
|
| 1305 |
+
return
|
| 1306 |
+
os.makedirs(os.path.dirname(SAM3_MODEL_PATH), exist_ok=True)
|
| 1307 |
+
tok = os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_HUB_TOKEN")
|
| 1308 |
+
f = hf_hub_download("facebook/sam3", "sam3.pt", token=tok)
|
| 1309 |
+
try:
|
| 1310 |
+
os.symlink(f, SAM3_MODEL_PATH)
|
| 1311 |
+
except OSError:
|
| 1312 |
+
shutil.copyfile(f, SAM3_MODEL_PATH)
|
| 1313 |
+
|
| 1314 |
+
|
| 1315 |
+
@spaces.GPU(duration=_duration_for_job, size=GPU_SIZE)
|
| 1316 |
+
def generate_from_image_video(
|
| 1317 |
+
image, pose_video, prompt, mode,
|
| 1318 |
+
sample_steps, guide_scale, sample_shift, seed,
|
| 1319 |
+
target_size, segment_len, segment_overlap,
|
| 1320 |
+
progress=gr.Progress(track_tqdm=True),
|
| 1321 |
+
):
|
| 1322 |
+
try:
|
| 1323 |
+
if image is None or pose_video is None:
|
| 1324 |
+
raise RuntimeError("Missing required input(s): reference image / pose video")
|
| 1325 |
+
target_w, target_h = [int(v) for v in str(target_size).split("x")]
|
| 1326 |
+
subdir = _tempfile.mkdtemp(prefix="scail_iv_")
|
| 1327 |
+
ref_path = os.path.join(subdir, "ref.png")
|
| 1328 |
+
drv_path = os.path.join(subdir, "driving.mp4")
|
| 1329 |
+
shutil.copyfile(image, ref_path)
|
| 1330 |
+
shutil.copyfile(pose_video, drv_path)
|
| 1331 |
+
progress(0.05, desc="Masking with SAM3 on GPU ...")
|
| 1332 |
+
ref_mask, drv_mask, rendered = _make_masks_e2e(
|
| 1333 |
+
subdir, "driving.mp4", text=("human", "character"), max_persons=1,
|
| 1334 |
+
)
|
| 1335 |
+
progress(0.35, desc="Running SCAIL-2 generation ...")
|
| 1336 |
+
output = _run_scail_job(
|
| 1337 |
+
ref_path, ref_mask, rendered, drv_mask,
|
| 1338 |
+
prompt, (mode == "replacement"), target_h, target_w,
|
| 1339 |
+
sample_steps, guide_scale, sample_shift, seed,
|
| 1340 |
+
segment_len, segment_overlap, progress=progress,
|
| 1341 |
+
)
|
| 1342 |
+
return output, "Done."
|
| 1343 |
+
except Exception:
|
| 1344 |
+
logging.exception("generate_from_image_video failed")
|
| 1345 |
+
return None, traceback.format_exc()
|
| 1346 |
+
|
| 1347 |
+
|
| 1348 |
def build_ui():
|
| 1349 |
examples = _existing_examples()
|
| 1350 |
example_names = list(examples.keys())
|
|
|
|
| 1461 |
outputs=[upload_output, upload_status],
|
| 1462 |
)
|
| 1463 |
|
| 1464 |
+
with gr.Tab("Image + Video (auto-mask)"):
|
| 1465 |
+
gr.Markdown(
|
| 1466 |
+
"Send only a **reference image** + **driving video**. "
|
| 1467 |
+
"The Space runs SAM3 masking on the GPU (SCAIL-Pose e2e) and generates."
|
| 1468 |
+
)
|
| 1469 |
+
iv_image = gr.Image(type="filepath", label="Reference image")
|
| 1470 |
+
iv_video = gr.Video(label="Driving / pose video")
|
| 1471 |
+
iv_prompt = gr.Textbox(label="Prompt", value="A young woman with natural body movement.")
|
| 1472 |
+
iv_mode = gr.Radio(["animation", "replacement"], value="animation", label="Mode")
|
| 1473 |
+
with gr.Row():
|
| 1474 |
+
iv_steps = gr.Slider(1, 50, value=8, step=1, label="Steps")
|
| 1475 |
+
iv_cfg = gr.Slider(1, 10, value=1.0, step=0.5, label="CFG (guide_scale)")
|
| 1476 |
+
iv_shift = gr.Slider(1, 10, value=3.0, step=0.5, label="Shift")
|
| 1477 |
+
with gr.Row():
|
| 1478 |
+
iv_seed = gr.Number(value=42, precision=0, label="Seed")
|
| 1479 |
+
iv_target = gr.Dropdown(
|
| 1480 |
+
["512x896", "896x512", "704x1280", "1280x704"],
|
| 1481 |
+
value="512x896", label="Target size",
|
| 1482 |
+
)
|
| 1483 |
+
iv_segment_len = gr.Number(value=81, precision=0, label="Segment length")
|
| 1484 |
+
iv_segment_overlap = gr.Number(value=5, precision=0, label="Segment overlap")
|
| 1485 |
+
run_iv = gr.Button("Generate (auto-mask)", variant="primary")
|
| 1486 |
+
iv_output = gr.Video(label="Output")
|
| 1487 |
+
iv_status = gr.Textbox(label="Run status")
|
| 1488 |
+
run_iv.click(
|
| 1489 |
+
generate_from_image_video,
|
| 1490 |
+
inputs=[
|
| 1491 |
+
iv_image, iv_video, iv_prompt, iv_mode,
|
| 1492 |
+
iv_steps, iv_cfg, iv_shift, iv_seed,
|
| 1493 |
+
iv_target, iv_segment_len, iv_segment_overlap,
|
| 1494 |
+
],
|
| 1495 |
+
outputs=[iv_output, iv_status],
|
| 1496 |
+
api_name="generate_from_image_video",
|
| 1497 |
+
)
|
| 1498 |
+
|
| 1499 |
with gr.Tab("Advanced Pack"):
|
| 1500 |
gr.Markdown(
|
| 1501 |
"Upload a `.zip` pack for multi-reference or multi-character inputs. "
|
|
|
|
| 1616 |
_prepare_runtime_for_startup()
|
| 1617 |
if PRELOAD_PIPELINE:
|
| 1618 |
_prepare_pipeline_for_startup()
|
| 1619 |
+
try:
|
| 1620 |
+
_ensure_sam3() # โหลด sam3.pt (ครั้งแรก) — ต้องมี secret HF_TOKEN ที่ได้สิทธิ์ facebook/sam3
|
| 1621 |
+
except Exception as _e:
|
| 1622 |
+
logging.warning("SAM3 weights not ready: %s", _e)
|
| 1623 |
build_ui().queue(max_size=8).launch(
|
| 1624 |
allowed_paths=[str(OUTPUT_DIR.resolve())],
|
| 1625 |
show_error=True,
|
requirements.txt
CHANGED
|
@@ -18,3 +18,6 @@ ftfy
|
|
| 18 |
tqdm
|
| 19 |
einops
|
| 20 |
numpy>=1.23.5,<2
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
tqdm
|
| 19 |
einops
|
| 20 |
numpy>=1.23.5,<2
|
| 21 |
+
|
| 22 |
+
ultralytics
|
| 23 |
+
moviepy>=1.0.3
|