Support loading model weights from local project folders

Add bgfilter/weights.py: resolve_model_source() maps a HuggingFace repo id
to a local folder under the weights dir (models/ by default, override with
BGFILTER_WEIGHTS_DIR) when one named after the repo basename exists; otherwise
the repo id is returned unchanged. Opt-in and backward compatible.

- vitmatte_infer.py / segmentation.py resolve model_name through it; anime-seg
  reads <dir>/isnetis.onnx directly instead of hf_hub_download when local.
- .gitignore: models/, model-cache/
- README: document bundling weights as plain project folders.

Verified: anime-seg loads from a local models/anime-seg/ folder offline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 02:50:19 +00:00
parent 7a87a00ca1
commit da4164e95a
5 changed files with 51 additions and 4 deletions
+2
View File
@@ -1,4 +1,6 @@
Samples/
Outputs/
models/
model-cache/
__pycache__/
*.py[cod]
+7
View File
@@ -205,6 +205,13 @@ D:\MiniConda\envs\lightML\python.exe scripts\smoke_samples.py `
`FileMetadataError`; (2) with `hf-xet` installed the Xet download path fails instantly.
Once the weights are cached, run offline with `HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1`
(as the service does in production).
- Bundling weights in the project — instead of the HF cache, drop each model into a
plain folder named after the repo basename under `models/`:
`models/vitmatte-base-composition-1k`, `models/BiRefNet`, `models/anime-seg`. The loader
prefers a matching local folder and falls back to the HF repo id / cache when absent, so
it is opt-in. Populate them with e.g. `hf download ZhengPeng7/BiRefNet --local-dir
models/BiRefNet`. Override the base directory with `BGFILTER_WEIGHTS_DIR`. `models/` is
gitignored.
- Foreground colour estimation uses pymatting's `estimate_foreground_ml` to propagate
clean foreground colour into semi-transparent edges before de-spill. Set
`foreground.method: unmix` to fall back to the legacy heuristic.
+9 -2
View File
@@ -1,9 +1,12 @@
from __future__ import annotations
import os
import numpy as np
from PIL import Image
from .settings import SegmentationSettings
from .weights import resolve_model_source
class BiRefNetSegmenter:
@@ -35,7 +38,7 @@ class BiRefNetSegmenter:
self.torch = torch
self.model = AutoModelForImageSegmentation.from_pretrained(
settings.model_name, trust_remote_code=True
resolve_model_source(settings.model_name), trust_remote_code=True
)
self.model.eval()
self.model.float() # checkpoint ships as fp16; force fp32 to match inputs
@@ -91,7 +94,11 @@ class AnimeSegSegmenter:
"pip install onnxruntime huggingface_hub"
) from exc
model_file = hf_hub_download(settings.model_name, "isnetis.onnx")
source = resolve_model_source(settings.model_name)
if os.path.isdir(source):
model_file = os.path.join(source, "isnetis.onnx")
else:
model_file = hf_hub_download(source, "isnetis.onnx")
providers = ["CPUExecutionProvider"]
if settings.device == "cuda" and "CUDAExecutionProvider" in ort.get_available_providers():
providers = ["CUDAExecutionProvider", "CPUExecutionProvider"]
+4 -2
View File
@@ -4,6 +4,7 @@ import numpy as np
from PIL import Image
from .settings import ModelSettings
from .weights import resolve_model_source
class ViTMatteRunner:
@@ -18,8 +19,9 @@ class ViTMatteRunner:
) from exc
self.torch = torch
self.processor = VitMatteImageProcessor.from_pretrained(settings.model_name)
self.model = VitMatteForImageMatting.from_pretrained(settings.model_name)
source = resolve_model_source(settings.model_name)
self.processor = VitMatteImageProcessor.from_pretrained(source)
self.model = VitMatteForImageMatting.from_pretrained(source)
self.device = self._resolve_device(settings.device)
self.model.to(self.device)
self.model.eval()
+29
View File
@@ -0,0 +1,29 @@
from __future__ import annotations
import os
from pathlib import Path
def weights_dir() -> Path:
"""Directory scanned for locally-bundled model weights.
Defaults to ``models`` (relative to the working directory); override with the
``BGFILTER_WEIGHTS_DIR`` environment variable.
"""
return Path(os.environ.get("BGFILTER_WEIGHTS_DIR", "models"))
def resolve_model_source(repo_id: str) -> str:
"""Map a HuggingFace repo id to a local weights folder when one is present.
If ``<weights_dir>/<basename>`` exists it is returned (so models can be shipped
as plain folders inside the project, e.g. ``ZhengPeng7/BiRefNet`` ->
``models/BiRefNet``); otherwise the repo id is returned unchanged so it loads
from the HF hub/cache as before. An absolute/existing path is passed through.
"""
if os.path.isdir(repo_id):
return repo_id
local = weights_dir() / repo_id.split("/")[-1]
if local.is_dir():
return str(local)
return repo_id