4danyone-rerun / fdanyone /foreground.py
pablovela5620's picture
Vendor the inference-path subset of the fdanyone package
1b2e96f
Raw History Blame
2.32 kB
"""Pinned BiRefNet inference over the canonical source clip."""
from __future__ import annotations
import gc
from pathlib import Path
import numpy as np
from PIL import Image
from fdanyone.config import FOREGROUND
def predict_foreground_masks(
frames: tuple[np.ndarray, ...],
model_path: str | Path,
device: str,
*,
batch_size: int = FOREGROUND.batch_size,
) -> np.ndarray:
"""Return full-raster 8-bit foreground masks for the canonical clip."""
import torch
from torchvision import transforms
from torchvision.transforms.functional import to_pil_image
from transformers import AutoModelForImageSegmentation
if not frames:
raise ValueError("Foreground inference requires at least one frame.")
if batch_size <= 0:
raise ValueError("batch_size must be positive.")
shape = frames[0].shape
if any(frame.dtype != np.uint8 or frame.shape != shape for frame in frames):
raise ValueError("Foreground frames must share one RGB uint8 raster.")
model = AutoModelForImageSegmentation.from_pretrained(
str(Path(model_path).expanduser().resolve()),
local_files_only=True,
trust_remote_code=True,
)
model = model.eval().half().to(device)
transform = transforms.Compose(
[
transforms.Resize(FOREGROUND.image_size),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
]
)
output: list[np.ndarray] = []
try:
for start in range(0, len(frames), batch_size):
images = [Image.fromarray(frame, mode="RGB") for frame in frames[start : start + batch_size]]
inputs = torch.stack([transform(image) for image in images]).to(device=device, dtype=torch.float16)
with torch.inference_mode():
predictions = model(inputs)[-1].sigmoid().cpu()
for image, prediction in zip(images, predictions, strict=True):
mask = to_pil_image(prediction).resize(image.size).convert("L")
output.append(np.asarray(mask, dtype=np.uint8).copy())
del inputs, predictions
finally:
del model
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
return np.stack(output)