Files
Azornes-Comfyui-LayerForge/python/matting.py
T

774 lines
29 KiB
Python

"""BiRefNet model discovery, download, inference, and HTTP endpoints."""
import hashlib
import os
import shutil
import threading
import traceback
from typing import ClassVar
import folder_paths
import torch
import torch.nn.functional as F
from aiohttp import web
from server import PromptServer
from torchvision import transforms
from .image_utils import convert_base64_to_tensor, convert_tensor_to_base64
from .node import log
_BIREFNET_REPOSITORY = "ZhengPeng7/BiRefNet"
_BIREFNET_FILENAME = "model.safetensors"
_BIREFNET_DEFAULT_LOCAL_FILENAME = "BiRefNet-general.safetensors"
_BIREFNET_PROJECT_URL = "https://github.com/ZhengPeng7/BiRefNet"
_BIREFNET_REMOTE_PREFIX = "remote:"
_BIREFNET_REMOTE_DIRECTORY = "layerforge_birefnet"
_BIREFNET_REQUIRED_KEYS = {
"bb.layers.1.blocks.0.attn.relative_position_index",
"bb.layers.2.blocks.17.attn.qkv.weight",
}
_BIREFNET_MODEL_CATALOG = (
{
"id": "general",
"label": "BiRefNet — General",
"description": "The best starting point for everyday images and general background removal.",
"repo_id": "ZhengPeng7/BiRefNet",
"filename": "model.safetensors",
"local_filename": "BiRefNet-general.safetensors",
},
{
"id": "high_resolution",
"label": "BiRefNet — High Resolution",
"description": "High-resolution segmentation for detailed edges and larger source images; it uses more memory.",
"repo_id": "ZhengPeng7/BiRefNet_HR",
"filename": "model.safetensors",
"local_filename": "BiRefNet-HR.safetensors",
},
{
"id": "portrait",
"label": "BiRefNet — Portrait",
"description": "Portrait matting for people, hair, and portrait-focused cutouts.",
"repo_id": "ZhengPeng7/BiRefNet-portrait",
"filename": "model.safetensors",
"local_filename": "BiRefNet-portrait.safetensors",
},
{
"id": "matting",
"label": "BiRefNet — Matting",
"description": "General matting with a focus on soft alpha edges such as hair and semi-transparent details.",
"repo_id": "ZhengPeng7/BiRefNet-matting",
"filename": "model.safetensors",
"local_filename": "BiRefNet-matting.safetensors",
},
{
"id": "high_resolution_matting",
"label": "BiRefNet — High Resolution Matting",
"description": "High-resolution general matting for fine details; it requires more memory than the standard matting model.",
"repo_id": "ZhengPeng7/BiRefNet_HR-matting",
"filename": "model.safetensors",
"local_filename": "BiRefNet-HR-matting.safetensors",
},
{
"id": "dynamic",
"label": "BiRefNet — Dynamic",
"description": "Dynamic-shape segmentation for inputs with varying aspect ratios and resolutions.",
"repo_id": "ZhengPeng7/BiRefNet_dynamic",
"filename": "model.safetensors",
"local_filename": "BiRefNet_dynamic.safetensors",
},
{
"id": "dynamic_matting",
"label": "BiRefNet — Dynamic Matting",
"description": "Dynamic-shape matting for arbitrary input sizes, with a focus on soft alpha edges.",
"repo_id": "ZhengPeng7/BiRefNet_dynamic-matting",
"filename": "model.safetensors",
"local_filename": "BiRefNet_dynamic-matting.safetensors",
},
{
"id": "hrsod",
"label": "BiRefNet — HRSOD",
"description": "High-resolution salient-object detection; useful when the main subject should stand out from its surroundings.",
"repo_id": "ZhengPeng7/BiRefNet-HRSOD",
"filename": "model.safetensors",
"local_filename": "BiRefNet-HRSOD.safetensors",
},
{
"id": "dis5k",
"label": "BiRefNet — DIS5K",
"description": "Dichotomous image segmentation trained for clean foreground/background separation.",
"repo_id": "ZhengPeng7/BiRefNet-DIS5K",
"filename": "model.safetensors",
"local_filename": "BiRefNet-DIS5K.safetensors",
},
{
"id": "cod",
"label": "BiRefNet — COD",
"description": "Camouflaged-object detection; use it for subjects that blend into their background.",
"repo_id": "ZhengPeng7/BiRefNet-COD",
"filename": "model.safetensors",
"local_filename": "BiRefNet-COD.safetensors",
},
)
def _get_comfy_birefnet_loader():
"""Return ComfyUI's native BiRefNet loader when it is available."""
try:
from comfy.bg_removal_model import load
return load
except Exception as error:
log.debug(f"Native ComfyUI BiRefNet loader is unavailable: {error}")
return None
def _get_birefnet_base_paths():
"""Return native ComfyUI and legacy locations that may contain BiRefNet."""
paths = []
get_folder_paths = getattr(folder_paths, "get_folder_paths", None)
if callable(get_folder_paths):
try:
paths.extend(get_folder_paths("background_removal"))
except (KeyError, TypeError):
pass
comfy_models_dir = getattr(folder_paths, "models_dir", None)
if comfy_models_dir:
paths.extend(
[
os.path.join(comfy_models_dir, "background_removal"),
os.path.join(comfy_models_dir, "RMBG", "BiRefNet"),
os.path.join(comfy_models_dir, "BiRefNet"),
]
)
unique_paths = []
seen = set()
for path in paths:
normalized = os.path.normcase(os.path.normpath(path))
if normalized not in seen:
seen.add(normalized)
unique_paths.append(path)
return unique_paths
def _is_native_birefnet_checkpoint(path):
"""Check the checkpoint signature without loading all weights into memory."""
if not os.path.isfile(path) or not path.lower().endswith(".safetensors"):
return False
try:
from safetensors import safe_open
with safe_open(path, framework="pt") as checkpoint:
return _BIREFNET_REQUIRED_KEYS.issubset(checkpoint.keys())
except Exception as error:
log.debug(f"Unable to inspect BiRefNet checkpoint {path}: {error}")
return False
def _iter_birefnet_checkpoint_paths():
"""Yield candidate checkpoints from native and legacy model directories."""
for base_path in _get_birefnet_base_paths():
if not os.path.isdir(base_path):
continue
for root, directories, files in os.walk(base_path):
directories[:] = [
directory
for directory in directories
if directory not in {
".git",
".no_exist",
".cache",
"__pycache__",
"refs",
"snapshots",
"blobs",
}
and not directory.startswith("models--")
]
for filename in sorted(files):
if filename.lower().endswith(".safetensors"):
yield os.path.join(root, filename)
def _find_local_birefnet_model(model_path=None):
"""Find a full BiRefNet checkpoint accepted by ComfyUI's native loader."""
candidates = []
seen = set()
for path in _iter_birefnet_checkpoint_paths():
normalized = os.path.normcase(os.path.normpath(path))
if normalized in seen:
continue
seen.add(normalized)
if _is_native_birefnet_checkpoint(path):
candidates.append(path)
if model_path and model_path != "auto":
requested = os.path.normcase(os.path.normpath(os.path.abspath(model_path)))
for candidate in candidates:
if os.path.normcase(os.path.normpath(os.path.abspath(candidate))) == requested:
return candidate
return None
if not candidates:
return None
priority = {
"birefnet.safetensors": 0,
"model.safetensors": 1,
"birefnet-general.safetensors": 2,
"birefnet-hr.safetensors": 3,
}
return min(
candidates,
key=lambda path: (priority.get(os.path.basename(path).lower(), 10), path.lower()),
)
def _get_birefnet_remote_model(model_path):
"""Resolve a client-facing remote model identifier from the fixed catalog."""
if not isinstance(model_path, str) or not model_path.startswith(_BIREFNET_REMOTE_PREFIX):
return None
model_id = model_path[len(_BIREFNET_REMOTE_PREFIX) :]
return next((model for model in _BIREFNET_MODEL_CATALOG if model["id"] == model_id), None)
def _get_birefnet_remote_checkpoint_path(model):
"""Return the managed local path for a catalog model."""
base_paths = _get_birefnet_base_paths()
if not base_paths:
return None
return os.path.join(
base_paths[0],
_BIREFNET_REMOTE_DIRECTORY,
model["id"],
model["local_filename"],
)
def _get_birefnet_remote_legacy_checkpoint_path(model):
"""Return the pre-friendly-name path used by earlier LayerForge builds."""
base_paths = _get_birefnet_base_paths()
if not base_paths:
return None
return os.path.join(
base_paths[0],
_BIREFNET_REMOTE_DIRECTORY,
model["id"],
model["filename"],
)
def _find_existing_birefnet_remote_checkpoint(model):
"""Find a managed remote checkpoint and migrate its old generic filename."""
checkpoint_path = _get_birefnet_remote_checkpoint_path(model)
if checkpoint_path and _is_native_birefnet_checkpoint(checkpoint_path):
return checkpoint_path
legacy_path = _get_birefnet_remote_legacy_checkpoint_path(model)
if not legacy_path or legacy_path == checkpoint_path or not _is_native_birefnet_checkpoint(legacy_path):
return None
try:
os.makedirs(os.path.dirname(checkpoint_path), exist_ok=True)
os.replace(legacy_path, checkpoint_path)
log.info(f"Renamed BiRefNet checkpoint to the friendly filename {checkpoint_path}")
return checkpoint_path
except OSError as error:
log.warning(f"Unable to rename BiRefNet checkpoint {legacy_path}: {error}")
return legacy_path
def _get_birefnet_model_options():
"""Return local checkpoints and official models available for download."""
_find_existing_birefnet_default_checkpoint()
local_options = []
seen = set()
managed_roots = [
os.path.normcase(os.path.normpath(os.path.join(base_path, _BIREFNET_REMOTE_DIRECTORY)))
for base_path in _get_birefnet_base_paths()
]
for path in _iter_birefnet_checkpoint_paths():
normalized = os.path.normcase(os.path.normpath(os.path.abspath(path)))
if normalized in seen or not _is_native_birefnet_checkpoint(path):
continue
if any(normalized == root or normalized.startswith(root + os.sep) for root in managed_roots):
continue
seen.add(normalized)
label = os.path.basename(path)
for base_path in _get_birefnet_base_paths():
try:
relative_path = os.path.relpath(path, base_path)
except ValueError:
continue
if relative_path == os.pardir or relative_path.startswith(os.pardir + os.sep):
continue
label = relative_path.replace(os.sep, "/")
break
local_options.append({
"path": path,
"label": label,
"source": "local",
"downloaded": True,
})
remote_options = []
for model in _BIREFNET_MODEL_CATALOG:
checkpoint_path = _find_existing_birefnet_remote_checkpoint(model)
remote_options.append({
"path": f"{_BIREFNET_REMOTE_PREFIX}{model['id']}",
"label": model["label"],
"description": model["description"],
"url": f"https://huggingface.co/{model['repo_id']}",
"project_url": _BIREFNET_PROJECT_URL,
"source": "remote",
"downloaded": bool(checkpoint_path and _is_native_birefnet_checkpoint(checkpoint_path)),
})
local_options.sort(key=lambda option: option["label"].lower())
return local_options + remote_options
def _get_birefnet_download_dir():
paths = _get_birefnet_base_paths()
if not paths:
raise RuntimeError("ComfyUI did not expose a background_removal model directory")
download_dir = paths[0]
os.makedirs(download_dir, exist_ok=True)
return download_dir
def _get_birefnet_default_checkpoint_path():
"""Return the friendly path used for the automatic General checkpoint."""
base_paths = _get_birefnet_base_paths()
if not base_paths:
return None
return os.path.join(base_paths[0], _BIREFNET_DEFAULT_LOCAL_FILENAME)
def _find_existing_birefnet_default_checkpoint():
"""Find the automatic checkpoint and migrate its old generic filename."""
checkpoint_path = _get_birefnet_default_checkpoint_path()
if checkpoint_path and _is_native_birefnet_checkpoint(checkpoint_path):
return checkpoint_path
base_paths = _get_birefnet_base_paths()
if not base_paths:
return None
legacy_path = os.path.join(base_paths[0], _BIREFNET_FILENAME)
if not checkpoint_path or not _is_native_birefnet_checkpoint(legacy_path):
return None
try:
os.makedirs(os.path.dirname(checkpoint_path), exist_ok=True)
os.replace(legacy_path, checkpoint_path)
log.info(f"Renamed the automatic BiRefNet checkpoint to {checkpoint_path}")
return checkpoint_path
except OSError as error:
log.warning(f"Unable to rename the automatic BiRefNet checkpoint {legacy_path}: {error}")
return legacy_path
def _download_birefnet_checkpoint(model=None):
"""Download and validate a full BiRefNet checkpoint into ComfyUI's model path."""
try:
from huggingface_hub import hf_hub_download
except ImportError as error:
raise RuntimeError(
"Automatic BiRefNet download requires the 'huggingface_hub' package. "
"Install the LayerForge requirements or place a compatible checkpoint in "
"ComfyUI/models/background_removal/."
) from error
if model is None:
repository = _BIREFNET_REPOSITORY
filename = _BIREFNET_FILENAME
download_dir = _get_birefnet_download_dir()
model_label = "BiRefNet — General"
target_path = os.path.join(download_dir, _BIREFNET_DEFAULT_LOCAL_FILENAME)
else:
repository = model["repo_id"]
filename = model["filename"]
download_dir = os.path.join(
_get_birefnet_download_dir(),
_BIREFNET_REMOTE_DIRECTORY,
model["id"],
)
os.makedirs(download_dir, exist_ok=True)
model_label = model["label"]
target_path = _get_birefnet_remote_checkpoint_path(model)
log.info(f"Downloading {model_label} from Hugging Face into {download_dir}...")
try:
downloaded_path = hf_hub_download(
repo_id=repository,
filename=filename,
local_dir=download_dir,
local_dir_use_symlinks=False,
)
except TypeError:
downloaded_path = hf_hub_download(
repo_id=repository,
filename=filename,
local_dir=download_dir,
)
if not _is_native_birefnet_checkpoint(downloaded_path):
raise RuntimeError(
f"Downloaded {model_label} is not a ComfyUI-compatible BiRefNet checkpoint: {downloaded_path}"
)
if target_path and os.path.normcase(os.path.abspath(downloaded_path)) != os.path.normcase(
os.path.abspath(target_path)
):
os.makedirs(os.path.dirname(target_path), exist_ok=True)
try:
os.replace(downloaded_path, target_path)
except OSError:
shutil.copy2(downloaded_path, target_path)
downloaded_path = target_path
if target_path and not _is_native_birefnet_checkpoint(downloaded_path):
raise RuntimeError(
f"Renamed {model_label} is not a ComfyUI-compatible BiRefNet checkpoint: {downloaded_path}"
)
log.info(f"{model_label} checkpoint is ready at {downloaded_path}")
return downloaded_path
def _ensure_birefnet_checkpoint(model_path=None):
remote_model = _get_birefnet_remote_model(model_path)
if remote_model:
checkpoint_path = _find_existing_birefnet_remote_checkpoint(remote_model)
if checkpoint_path:
return checkpoint_path
return _download_birefnet_checkpoint(remote_model)
if not model_path or model_path == "auto":
_find_existing_birefnet_default_checkpoint()
checkpoint_path = _find_local_birefnet_model(model_path)
if checkpoint_path:
return checkpoint_path
if model_path and model_path != "auto":
raise RuntimeError("The selected BiRefNet checkpoint is not available or is not compatible with ComfyUI.")
return _download_birefnet_checkpoint()
class BiRefNetMatting:
"""Adapter around ComfyUI's native BiRefNet loader."""
_model_cache: ClassVar[dict] = {}
_model_cache_lock = threading.Lock()
def __init__(self):
self.model = None
self.model_path = None
def load_model(self, model_path=None):
loader = _get_comfy_birefnet_loader()
if loader is None:
raise RuntimeError(
"This ComfyUI version does not provide the native BiRefNet background-removal loader."
)
checkpoint_path = _ensure_birefnet_checkpoint(model_path)
with self._model_cache_lock:
if checkpoint_path not in self._model_cache:
log.info(f"Loading BiRefNet with ComfyUI's native loader from {checkpoint_path}")
model = loader(checkpoint_path)
if model is None:
raise RuntimeError(
f"ComfyUI did not recognize the BiRefNet checkpoint: {checkpoint_path}"
)
self._model_cache[checkpoint_path] = model
else:
log.debug(f"Using cached native BiRefNet model from {checkpoint_path}")
self.model = self._model_cache[checkpoint_path]
self.model_path = checkpoint_path
return self.model
def preprocess_image(self, image):
if not isinstance(image, torch.Tensor):
image = transforms.ToTensor()(image).unsqueeze(0)
if image.dim() == 3:
if image.shape[0] in (1, 3, 4):
image = image.movedim(0, -1).unsqueeze(0)
else:
image = image.unsqueeze(0)
elif image.dim() == 4 and image.shape[1] in (1, 3, 4):
image = image.movedim(1, -1)
if image.dim() != 4 or image.shape[-1] not in (1, 3, 4):
raise ValueError(f"Expected an image tensor in BCHW or BHWC format, got {tuple(image.shape)}")
if image.shape[-1] == 1:
image = image.expand(-1, -1, -1, 3)
elif image.shape[-1] == 4:
image = image[..., :3]
return image.to(dtype=torch.float32).contiguous()
def execute(self, image, model_path, threshold=0.5, refinement=1, mode="remove_background"):
try:
PromptServer.instance.send_sync("matting_status", {"status": "processing"})
del refinement
if mode not in {"remove_background", "remove_foreground", "mask_only"}:
raise ValueError(f"Unsupported matting mode: {mode}")
threshold = float(threshold)
if not 0.0 <= threshold <= 1.0:
raise ValueError("Matting threshold must be between 0 and 1")
image_tensor = self.preprocess_image(image)
original_size = (image_tensor.shape[1], image_tensor.shape[2])
log.debug(f"Original size: {original_size}")
self.load_model(model_path)
log.debug(f"Processed image shape: {image_tensor.shape}, dtype: {image_tensor.dtype}")
with torch.no_grad():
result = self.model.encode_image(image_tensor)
if result.dim() == 3:
result = result.unsqueeze(1)
elif result.dim() == 2:
result = result.unsqueeze(0).unsqueeze(0)
else:
raise ValueError(f"Unexpected BiRefNet output shape: {tuple(result.shape)}")
result = result.to(device=image_tensor.device, dtype=torch.float32)
if result.shape[-2:] != original_size:
result = F.interpolate(
result,
size=original_size,
mode="bilinear",
align_corners=False,
)
result = result.clamp(0.0, 1.0)
log.debug(f"Native BiRefNet output shape: {result.shape}, dtype: {result.dtype}")
if threshold > 0:
result = (result > threshold).to(dtype=torch.float32)
alpha_mask = 1.0 - result if mode == "remove_foreground" else result
if mode == "mask_only":
masked_image = alpha_mask.expand(-1, 3, -1, -1)
else:
masked_image = image_tensor.movedim(-1, 1) * alpha_mask
PromptServer.instance.send_sync("matting_status", {"status": "completed"})
return masked_image, alpha_mask
except Exception:
PromptServer.instance.send_sync("matting_status", {"status": "error"})
raise
@classmethod
def IS_CHANGED(cls, image, model_path, threshold, refinement, mode="remove_background"):
digest = hashlib.md5()
digest.update(str(image).encode())
digest.update(str(model_path).encode())
digest.update(str(threshold).encode())
digest.update(str(refinement).encode())
digest.update(str(mode).encode())
return digest.hexdigest()
_matting_lock = None
async def check_matting_model(request):
"""Report whether the selected native BiRefNet checkpoint is ready."""
try:
if _get_comfy_birefnet_loader() is None:
return web.json_response({
"available": False,
"reason": "unsupported_comfyui",
"message": "This ComfyUI version does not provide the native BiRefNet background-removal loader.",
})
model_options = _get_birefnet_model_options()
requested_model = request.query.get("model_path") or "auto"
remote_model = _get_birefnet_remote_model(requested_model)
if remote_model:
local_model_path = _find_existing_birefnet_remote_checkpoint(remote_model)
if local_model_path:
log.info(f"Selected BiRefNet model is ready at {local_model_path}")
return web.json_response({
"available": True,
"reason": "ready",
"message": "Selected model is ready to use",
"model_path": local_model_path,
"selected_model": remote_model["label"],
"models": model_options,
})
log.info(f"Selected BiRefNet model is not downloaded: {remote_model['label']}")
return web.json_response({
"available": False,
"reason": "not_downloaded",
"message": f"{remote_model['label']} will be downloaded automatically on first use.",
"model_path": requested_model,
"selected_model": remote_model["label"],
"models": model_options,
})
if requested_model != "auto":
local_model_path = _find_local_birefnet_model(requested_model)
if local_model_path:
log.info(f"Selected BiRefNet model is ready at {local_model_path}")
return web.json_response({
"available": True,
"reason": "ready",
"message": "Selected model is ready to use",
"model_path": local_model_path,
"selected_model": local_model_path,
"models": model_options,
})
return web.json_response({
"available": False,
"reason": "selected_model_unavailable",
"message": "The selected BiRefNet checkpoint is not available or is not compatible with ComfyUI.",
"model_path": requested_model,
"models": model_options,
})
local_model_path = _find_local_birefnet_model()
if local_model_path:
log.info(f"BiRefNet model files detected at {local_model_path}")
return web.json_response({
"available": True,
"reason": "ready",
"message": "Model is ready to use",
"model_path": local_model_path,
"models": model_options,
})
searched_paths = _get_birefnet_base_paths()
log.info(f"BiRefNet model not found in any of: {searched_paths}")
return web.json_response({
"available": False,
"reason": "not_downloaded",
"message": "The BiRefNet checkpoint will be downloaded automatically on first use (requires internet connection).",
"model_path": searched_paths[0] if searched_paths else None,
"models": model_options,
})
except Exception as error:
log.error(f"Error checking matting model: {error}")
return web.json_response({
"available": False,
"reason": "error",
"message": f"Error checking model status: {error}",
}, status=500)
async def matting(request):
global _matting_lock
if _matting_lock is not None:
log.warning("Matting already in progress, rejecting request")
return web.json_response({
"error": "Another matting operation is in progress",
"details": "Please wait for the current operation to complete",
}, status=429)
_matting_lock = True
try:
log.info("Received matting request")
data = await request.json()
matting_instance = BiRefNetMatting()
image_tensor, original_alpha = convert_base64_to_tensor(data["image"])
log.debug(f"Input image shape: {image_tensor.shape}")
mode = data.get("mode", "remove_background")
model_path = data.get("model_path") or "auto"
matted_image, alpha_mask = matting_instance.execute(
image_tensor,
model_path,
threshold=data.get("threshold", 0.5),
refinement=data.get("refinement", 1),
mode=mode,
)
if mode == "mask_only":
result_image = convert_tensor_to_base64(alpha_mask)
else:
result_image = convert_tensor_to_base64(matted_image, alpha_mask, original_alpha)
result_mask = convert_tensor_to_base64(alpha_mask)
# Draw Mask uses white pixels as the area to remove, while matting uses
# white pixels as the area to keep.
draw_mask = 1.0 - alpha_mask
return web.json_response({
"matted_image": result_image,
"alpha_mask": result_mask,
"draw_mask": convert_tensor_to_base64(draw_mask),
"mode": mode,
"model_path": matting_instance.model_path,
})
except RuntimeError as error:
log.error(f"Runtime error during matting: {error}")
return web.json_response({
"error": "Matting Model Error",
"details": str(error),
}, status=500)
except Exception as error:
log.exception(f"Error in matting endpoint: {error}")
error_text = str(error).lower()
if any(marker in error_text for marker in (
"offline", "connection", "timed out", "huggingface", "localentrynotfound"
)):
return web.json_response({
"error": "Network Connection Error",
"details": "Failed to download the BiRefNet model from Hugging Face. Please check your internet connection.",
}, status=400)
return web.json_response({
"error": "An unexpected error occurred",
"details": traceback.format_exc(),
}, status=500)
finally:
_matting_lock = None
log.debug("Matting lock released")
def register_matting_routes():
"""Register matting endpoints without import-time decorators."""
PromptServer.instance.routes.get("/matting/check-model")(check_matting_model)
PromptServer.instance.routes.post("/matting")(matting)
__all__ = [
"_BIREFNET_DEFAULT_LOCAL_FILENAME",
"_BIREFNET_FILENAME",
"_BIREFNET_MODEL_CATALOG",
"_BIREFNET_PROJECT_URL",
"_BIREFNET_REMOTE_PREFIX",
"BiRefNetMatting",
"_download_birefnet_checkpoint",
"_ensure_birefnet_checkpoint",
"_find_existing_birefnet_default_checkpoint",
"_find_existing_birefnet_remote_checkpoint",
"_find_local_birefnet_model",
"_get_birefnet_model_options",
"_get_comfy_birefnet_loader",
"_is_native_birefnet_checkpoint",
"check_matting_model",
"matting",
"register_matting_routes",
]