import pathlib import sys import time from enum import Enum from typing import IO, cast import aiohttp import click import filetype import uvicorn from asyncer import asyncify from fastapi import Depends, FastAPI, File, Form, Query from fastapi.middleware.cors import CORSMiddleware from starlette.responses import Response from tqdm import tqdm from watchdog.events import FileSystemEvent, FileSystemEventHandler from watchdog.observers import Observer from . import _version from .bg import remove from .session_base import BaseSession from .session_factory import new_session @click.group() @click.version_option(version=_version.get_versions()["version"]) def main() -> None: pass @main.command(help="for a file as input") @click.option( "-m", "--model", default="u2net", type=click.Choice( ["u2net", "u2netp", "u2net_human_seg", "u2net_cloth_seg", "silueta"] ), show_default=True, show_choices=True, help="model name", ) @click.option( "-a", "--alpha-matting", is_flag=True, show_default=True, help="use alpha matting", ) @click.option( "-af", "--alpha-matting-foreground-threshold", default=240, type=int, show_default=True, help="trimap fg threshold", ) @click.option( "-ab", "--alpha-matting-background-threshold", default=10, type=int, show_default=True, help="trimap bg threshold", ) @click.option( "-ae", "--alpha-matting-erode-size", default=10, type=int, show_default=True, help="erode size", ) @click.option( "-om", "--only-mask", is_flag=True, show_default=True, help="output only the mask", ) @click.option( "-ppm", "--post-process-mask", is_flag=True, show_default=True, help="post process the mask", ) @click.argument( "input", default=(None if sys.stdin.isatty() else "-"), type=click.File("rb") ) @click.argument( "output", default=(None if sys.stdin.isatty() else "-"), type=click.File("wb", lazy=True), ) def i(model: str, input: IO, output: IO, **kwargs) -> None: output.write(remove(input.read(), session=new_session(model), **kwargs)) @main.command(help="for a folder as input") @click.option( "-m", "--model", default="u2net", type=click.Choice( ["u2net", "u2netp", "u2net_human_seg", "u2net_cloth_seg", "silueta"] ), show_default=True, show_choices=True, help="model name", ) @click.option( "-a", "--alpha-matting", is_flag=True, show_default=True, help="use alpha matting", ) @click.option( "-af", "--alpha-matting-foreground-threshold", default=240, type=int, show_default=True, help="trimap fg threshold", ) @click.option( "-ab", "--alpha-matting-background-threshold", default=10, type=int, show_default=True, help="trimap bg threshold", ) @click.option( "-ae", "--alpha-matting-erode-size", default=10, type=int, show_default=True, help="erode size", ) @click.option( "-om", "--only-mask", is_flag=True, show_default=True, help="output only the mask", ) @click.option( "-ppm", "--post-process-mask", is_flag=True, show_default=True, help="post process the mask", ) @click.option( "-w", "--watch", default=False, is_flag=True, show_default=True, help="watches a folder for changes", ) @click.argument( "input", type=click.Path( exists=True, path_type=pathlib.Path, file_okay=False, dir_okay=True, readable=True, ), ) @click.argument( "output", type=click.Path( exists=False, path_type=pathlib.Path, file_okay=False, dir_okay=True, writable=True, ), ) def p( model: str, input: pathlib.Path, output: pathlib.Path, watch: bool, **kwargs ) -> None: session = new_session(model) def process(each_input: pathlib.Path) -> None: try: mimetype = filetype.guess(each_input) if mimetype is None: return if mimetype.mime.find("image") < 0: return each_output = (output / each_input.name).with_suffix(".png") each_output.parents[0].mkdir(parents=True, exist_ok=True) if not each_output.exists(): each_output.write_bytes( cast( bytes, remove(each_input.read_bytes(), session=session, **kwargs), ) ) if watch: print( f"processed: {each_input.absolute()} -> {each_output.absolute()}" ) except Exception as e: print(e) inputs = list(input.glob("**/*")) if not watch: inputs = tqdm(inputs) for each_input in inputs: if not each_input.is_dir(): process(each_input) if watch: observer = Observer() class EventHandler(FileSystemEventHandler): def on_any_event(self, event: FileSystemEvent) -> None: if not ( event.is_directory or event.event_type in ["deleted", "closed"] ): process(pathlib.Path(event.src_path)) event_handler = EventHandler() observer.schedule(event_handler, input, recursive=False) observer.start() try: while True: time.sleep(1) finally: observer.stop() observer.join() @main.command(help="for a http server") @click.option( "-p", "--port", default=5000, type=int, show_default=True, help="port", ) @click.option( "-l", "--log_level", default="info", type=str, show_default=True, help="log level", ) @click.option( "-t", "--threads", default=None, type=int, show_default=True, help="number of worker threads", ) def s(port: int, log_level: str, threads: int) -> None: sessions: dict[str, BaseSession] = {} tags_metadata = [ { "name": "Background Removal", "description": "Endpoints that perform background removal with different image sources.", "externalDocs": { "description": "GitHub Source", "url": "https://github.com/danielgatis/rembg", }, }, ] app = FastAPI( title="Rembg", description="Rembg is a tool to remove images background. That is it.", version=_version.get_versions()["version"], contact={ "name": "Daniel Gatis", "url": "https://github.com/danielgatis", "email": "danielgatis@gmail.com", }, license_info={ "name": "MIT License", "url": "https://github.com/danielgatis/rembg/blob/main/LICENSE.txt", }, openapi_tags=tags_metadata, ) app.add_middleware( CORSMiddleware, allow_credentials=True, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) class ModelType(str, Enum): u2net = "u2net" u2netp = "u2netp" u2net_human_seg = "u2net_human_seg" u2net_cloth_seg = "u2net_cloth_seg" silueta = "silueta" class CommonQueryParams: def __init__( self, model: ModelType = Query( default=ModelType.u2net, description="Model to use when processing image", ), a: bool = Query(default=False, description="Enable Alpha Matting"), af: int = Query( default=240, ge=0, le=255, description="Alpha Matting (Foreground Threshold)", ), ab: int = Query( default=10, ge=0, le=255, description="Alpha Matting (Background Threshold)", ), ae: int = Query( default=10, ge=0, description="Alpha Matting (Erode Structure Size)" ), om: bool = Query(default=False, description="Only Mask"), ppm: bool = Query(default=False, description="Post Process Mask"), ): self.model = model self.a = a self.af = af self.ab = ab self.ae = ae self.om = om self.ppm = ppm class CommonQueryPostParams: def __init__( self, model: ModelType = Form( default=ModelType.u2net, description="Model to use when processing image", ), a: bool = Form(default=False, description="Enable Alpha Matting"), af: int = Form( default=240, ge=0, le=255, description="Alpha Matting (Foreground Threshold)", ), ab: int = Form( default=10, ge=0, le=255, description="Alpha Matting (Background Threshold)", ), ae: int = Form( default=10, ge=0, description="Alpha Matting (Erode Structure Size)" ), om: bool = Form(default=False, description="Only Mask"), ppm: bool = Form(default=False, description="Post Process Mask"), ): self.model = model self.a = a self.af = af self.ab = ab self.ae = ae self.om = om self.ppm = ppm def im_without_bg(content: bytes, commons: CommonQueryParams) -> Response: return Response( remove( content, session=sessions.setdefault( commons.model.value, new_session(commons.model.value) ), alpha_matting=commons.a, alpha_matting_foreground_threshold=commons.af, alpha_matting_background_threshold=commons.ab, alpha_matting_erode_size=commons.ae, only_mask=commons.om, post_process_mask=commons.ppm, ), media_type="image/png", ) @app.on_event("startup") def startup(): if threads is not None: from anyio import CapacityLimiter from anyio.lowlevel import RunVar RunVar("_default_thread_limiter").set(CapacityLimiter(threads)) @app.get( path="/", tags=["Background Removal"], summary="Remove from URL", description="Removes the background from an image obtained by retrieving an URL.", ) async def get_index( url: str = Query( default=..., description="URL of the image that has to be processed." ), commons: CommonQueryParams = Depends(), ): async with aiohttp.ClientSession() as session: async with session.get(url) as response: file = await response.read() return await asyncify(im_without_bg)(file, commons) @app.post( path="/", tags=["Background Removal"], summary="Remove from Stream", description="Removes the background from an image sent within the request itself.", ) async def post_index( file: bytes = File( default=..., description="Image file (byte stream) that has to be processed.", ), commons: CommonQueryPostParams = Depends(), ): return await asyncify(im_without_bg)(file, commons) uvicorn.run(app, host="0.0.0.0", port=port, log_level=log_level)