| from __future__ import annotations |
|
|
| import os |
| import threading |
| from dataclasses import dataclass |
|
|
| import numpy as np |
| from PIL import Image |
|
|
| try: |
| import spaces |
|
|
| gpu_task = spaces.GPU(duration=180) |
| except ImportError: |
| def gpu_task(function): |
| return function |
|
|
|
|
| MODEL_ID = os.getenv("MODEL_ID", "ZhengPeng7/BiRefNet") |
| MODEL_REVISION = os.getenv( |
| "MODEL_REVISION", "e2bf8e4460fc8fa32bba5ea4d94b3233d367b0e4" |
| ) |
| MODEL_INPUT_SIZE = int(os.getenv("MODEL_INPUT_SIZE", "1024")) |
|
|
| _runtime: "BiRefNetRuntime | None" = None |
| _load_lock = threading.Lock() |
|
|
|
|
| @dataclass |
| class BiRefNetRuntime: |
| model: object |
| device: object |
| dtype: object |
|
|
| @classmethod |
| def load(cls) -> "BiRefNetRuntime": |
| import torch |
| from transformers import AutoModelForImageSegmentation |
|
|
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| dtype = torch.float16 if device.type == "cuda" else torch.float32 |
| torch.set_float32_matmul_precision("high") |
| model = AutoModelForImageSegmentation.from_pretrained( |
| MODEL_ID, |
| revision=MODEL_REVISION, |
| trust_remote_code=True, |
| ) |
| model.to(device=device, dtype=dtype) |
| model.eval() |
| return cls(model=model, device=device, dtype=dtype) |
|
|
| def predict(self, image: Image.Image) -> Image.Image: |
| import torch |
| from torchvision.transforms import functional as TF |
|
|
| boxed, content_box = _letterbox(image, MODEL_INPUT_SIZE) |
| tensor = TF.to_tensor(boxed) |
| tensor = TF.normalize( |
| tensor, |
| mean=(0.485, 0.456, 0.406), |
| std=(0.229, 0.224, 0.225), |
| ).unsqueeze(0) |
| tensor = tensor.to(device=self.device, dtype=self.dtype) |
|
|
| with torch.inference_mode(): |
| prediction = self.model(tensor) |
| logits = _last_tensor(prediction) |
| probability = logits.sigmoid()[0].squeeze().float().cpu().numpy() |
|
|
| probability = np.clip(probability * 255.0, 0, 255).astype(np.uint8) |
| square_mask = Image.fromarray(probability, mode="L") |
| content_mask = square_mask.crop(content_box) |
| return content_mask.resize(image.size, Image.Resampling.LANCZOS) |
|
|
|
|
| def _letterbox(image: Image.Image, size: int) -> tuple[Image.Image, tuple[int, int, int, int]]: |
| scale = min(size / image.width, size / image.height) |
| resized_size = (max(1, round(image.width * scale)), max(1, round(image.height * scale))) |
| resized = image.resize(resized_size, Image.Resampling.LANCZOS) |
| |
| canvas = Image.new("RGB", (size, size), (124, 116, 104)) |
| left = (size - resized.width) // 2 |
| top = (size - resized.height) // 2 |
| canvas.paste(resized, (left, top)) |
| return canvas, (left, top, left + resized.width, top + resized.height) |
|
|
|
|
| def _last_tensor(value): |
| import torch |
|
|
| if torch.is_tensor(value): |
| return value |
| if hasattr(value, "logits"): |
| return _last_tensor(value.logits) |
| if isinstance(value, (tuple, list)) and value: |
| for item in reversed(value): |
| try: |
| return _last_tensor(item) |
| except (TypeError, ValueError): |
| continue |
| raise TypeError("BiRefNet beklenmeyen bir çıktı biçimi döndürdü.") |
|
|
|
|
| def get_runtime() -> BiRefNetRuntime: |
| global _runtime |
| if _runtime is None: |
| with _load_lock: |
| if _runtime is None: |
| _runtime = BiRefNetRuntime.load() |
| return _runtime |
|
|
|
|
| @gpu_task |
| def predict_mask(image: Image.Image) -> Image.Image: |
| """Run segmentation on ZeroGPU/classical GPU, with transparent CPU fallback.""" |
| return get_runtime().predict(image) |
|
|
|
|