| import re |
| from typing import Literal |
|
|
| from PIL import Image |
| from rembg import new_session, remove |
|
|
| from pipeline.background_web import fetch_public_background |
|
|
| BACKGROUND_TRIGGERS = ( |
| "replace background", |
| "change background", |
| "swap background", |
| "new background", |
| "replace by", |
| ) |
|
|
| QUERY_PREFIXES = ( |
| r"replace background with\s+", |
| r"replace background by\s+", |
| r"replace by\s+", |
| r"change background to\s+", |
| r"change background with\s+", |
| r"new background\s+", |
| r"swap background with\s+", |
| r"swap background for\s+", |
| ) |
|
|
| RANDOM_QUERY_WORDS = {"anything", "something", "random", "whatever", "anything at all"} |
|
|
| TRANSFORM_TAIL = re.compile( |
| r"\s+and\s+(rotate|rotation|turn|brighter|brighten|darker|darken|more contrast|" |
| r"less contrast|flip|blur|sepia|saturate|invert|warmer|cooler|cover .+)$", |
| re.I, |
| ) |
|
|
| _session = None |
|
|
|
|
| def warmup() -> None: |
| global _session |
| if _session is None: |
| _session = new_session("u2netp") |
|
|
|
|
| def is_background_replace_instruction(instruction: str) -> bool: |
| lowered = instruction.lower() |
| return any(trigger in lowered for trigger in BACKGROUND_TRIGGERS) |
|
|
|
|
| def parse_background_query(instruction: str) -> str | None | Literal[False]: |
| """ |
| Return the Openverse search query, None for random, or False if not a bg-replace instruction. |
| """ |
| if not is_background_replace_instruction(instruction): |
| return False |
|
|
| for pattern in QUERY_PREFIXES: |
| match = re.search(pattern + r"(.+)", instruction, re.I) |
| if not match: |
| continue |
| query = _clean_query(match.group(1)) |
| if query and query.lower() not in RANDOM_QUERY_WORDS: |
| return query |
| return None |
|
|
| return None |
|
|
|
|
| def _clean_query(text: str) -> str: |
| query = text.strip().strip('"').strip("'") |
| query = TRANSFORM_TAIL.sub("", query).strip() |
| query = re.sub(r"^(a|an|the)\s+", "", query, flags=re.I) |
| return query.strip() |
|
|
|
|
| def pick_background( |
| query: str | None, |
| size: tuple[int, int], |
| ) -> tuple[Image.Image, dict]: |
| return fetch_public_background(query, size) |
|
|
|
|
| def extract_foreground(image: Image.Image) -> Image.Image: |
| warmup() |
| fg = remove(image.convert("RGB"), session=_session) |
| return fg.convert("RGBA") |
|
|
|
|
| def replace_background( |
| image: Image.Image, |
| query: str | None = None, |
| instruction: str = "", |
| ) -> tuple[Image.Image, dict]: |
| if instruction and query is None and is_background_replace_instruction(instruction): |
| parsed = parse_background_query(instruction) |
| if parsed is not False: |
| query = parsed |
|
|
| fg = extract_foreground(image) |
| bg, pick_meta = pick_background(query, fg.size) |
| canvas = bg.convert("RGBA") |
| canvas.paste(fg, (0, 0), fg) |
| return canvas, { |
| "op": "replace_background", |
| "requested_query": query, |
| **pick_meta, |
| } |
|
|