import io
from typing import Any
import gradio as gr
import httpx
import pillow_heif
from environs import Env
from gradio_image_annotation import image_annotator
from gradio_imageslider import ImageSlider
from PIL import Image
pillow_heif.register_heif_opener()
pillow_heif.register_avif_opener()
env = Env()
env.read_env()
with env.prefixed("ERASER_"):
API_URL: str = str(env.str("API_URL", "https://spaces.finegrain.ai/eraser"))
API_KEY: str | None = env.str("API_KEY", None)
CA_BUNDLE: str | None = env.str("CA_BUNDLE", None)
auth = None if API_KEY is None else httpx.BasicAuth("hf", API_KEY)
def resize(image: Image.Image, shortest_side: int = 768) -> Image.Image:
if image.width <= shortest_side and image.height <= shortest_side:
return image
if image.width < image.height:
return image.resize(size=(shortest_side, int(shortest_side * image.height / image.width)))
return image.resize(size=(int(shortest_side * image.width / image.height), shortest_side))
def process_bbox(
prompts: dict[str, Any],
request: gr.Request | None,
) -> tuple[Image.Image, Image.Image]:
assert isinstance(img := prompts["image"], Image.Image)
assert isinstance(boxes := prompts["boxes"], list)
assert len(boxes) == 1
assert isinstance(box := boxes[0], dict)
headers = {}
if request: # avoid DOS - can be None despite type hint!
client_ip = request.headers.get("x-forwarded-for") or request.client.host
headers = {"X-HF-Client-IP": client_ip}
resized_img = resize(img)
bbox = [box[k] for k in ["xmin", "ymin", "xmax", "ymax"]]
if resized_img.width != img.width:
bbox = [int(v * resized_img.width / img.width) for v in bbox]
with io.BytesIO() as f:
resized_img.save(f, format="JPEG")
r = httpx.post(
API_URL,
data={"bbox": ",".join([str(v) for v in bbox])},
files={"file": f},
verify=CA_BUNDLE or True,
timeout=30.0,
auth=auth,
headers=headers,
)
r.raise_for_status()
output_image = Image.open(io.BytesIO(r.content))
return (img, output_image)
def on_change_bbox(prompts: dict[str, Any] | None):
return gr.update(interactive=prompts is not None and len(prompts["boxes"]) > 0)
def process_prompt(
img: Image.Image,
prompt: str,
request: gr.Request | None,
) -> tuple[Image.Image, Image.Image]:
headers = {}
if request: # avoid DOS - can be None despite type hint!
client_ip = request.headers.get("x-forwarded-for") or request.client.host
headers = {"X-HF-Client-IP": client_ip}
resized_img = resize(img)
with io.BytesIO() as f:
resized_img.save(f, format="JPEG")
r = httpx.post(
API_URL,
data={"prompt": prompt},
files={"file": f},
verify=CA_BUNDLE or True,
timeout=30.0,
auth=auth,
headers=headers,
)
r.raise_for_status()
output_image = Image.open(io.BytesIO(r.content))
return (img, output_image)
def on_change_prompt(img: Image.Image | None, prompt: str | None):
return gr.update(interactive=bool(img and prompt))
TITLE = """
🚀 For an optimized version of this space, try out the
Finegrain Editor! You'll find there all our AI tools made available in a nice UI. 🚀
Object Eraser Powered By Refiners
Erase any object from your image just by naming it — no manual work required!
Not only will the object disappear, but so will its effects on the scene, like shadows or reflections.
This space is powered by Refiners, our open source micro-framework for simple foundation model adaptation.
If you enjoyed it, please consider starring Refiners on GitHub!
"""
with gr.Blocks() as demo:
gr.HTML(TITLE)
with gr.Tab("By prompt", id="tab_prompt"):
with gr.Row():
with gr.Column():
iimg = gr.Image(type="pil", label="Input")
prompt = gr.Textbox(label="What should we erase?")
with gr.Column():
oimg = ImageSlider(label="Output")
with gr.Row():
btn = gr.ClearButton(components=[oimg], value="Erase Object", interactive=False)
for inp in [iimg, prompt]:
inp.change(
fn=on_change_prompt,
inputs=[iimg, prompt],
outputs=[btn],
)
btn.click(
fn=process_prompt,
inputs=[iimg, prompt],
outputs=[oimg],
api_name=False,
)
examples = [
[
"examples/white-towels-rattan-basket-white-table-with-bright-room-background.jpg",
"soap",
],
[
"examples/interior-decor-with-mirror-potted-plant.jpg",
"potted plant",
],
[
"examples/detail-ball-basketball-court-sunset.jpg",
"basketball",
],
[
"examples/still-life-device-table_23-2150994394.jpg",
"glass of water",
],
[
"examples/knife-fork-green-checkered-napkin_140725-63576.jpg",
"knife and fork",
],
[
"examples/city-night-with-architecture-vibrant-lights_23-2149836930.jpg",
"frontmost black car on right lane",
],
[
"examples/close-up-coffee-latte-wooden-table_23-2147893063.jpg",
"coffee cup on plate",
],
[
"examples/empty-chair-with-vase-plant_74190-2078.jpg",
"chair",
],
]
ex = gr.Examples(
examples=examples,
inputs=[iimg, prompt],
outputs=[oimg],
fn=process_prompt,
cache_examples=True,
)
with gr.Tab("By bounding box", id="tab_bb"):
with gr.Row():
with gr.Column():
annotator = image_annotator(
image_type="pil",
disable_edit_boxes=True,
show_download_button=False,
show_share_button=False,
single_box=True,
label="Input",
)
with gr.Column():
oimg = ImageSlider(label="Output")
with gr.Row():
btn = gr.ClearButton(components=[oimg], value="Erase Object", interactive=False)
annotator.change(
fn=on_change_bbox,
inputs=[annotator],
outputs=[btn],
)
btn.click(
fn=process_bbox,
inputs=[annotator],
outputs=[oimg],
api_name=False,
)
examples = [
{
"image": "examples/white-towels-rattan-basket-white-table-with-bright-room-background.jpg",
"boxes": [{"xmin": 836, "ymin": 475, "xmax": 1125, "ymax": 1013}],
},
{
"image": "examples/interior-decor-with-mirror-potted-plant.jpg",
"boxes": [{"xmin": 47, "ymin": 907, "xmax": 397, "ymax": 1633}],
},
{
"image": "examples/detail-ball-basketball-court-sunset.jpg",
"boxes": [{"xmin": 673, "ymin": 954, "xmax": 911, "ymax": 1186}],
},
{
"image": "examples/still-life-device-table_23-2150994394.jpg",
"boxes": [{"xmin": 429, "ymin": 586, "xmax": 571, "ymax": 834}],
},
{
"image": "examples/knife-fork-green-checkered-napkin_140725-63576.jpg",
"boxes": [{"xmin": 972, "ymin": 226, "xmax": 1092, "ymax": 1023}],
},
{
"image": "examples/city-night-with-architecture-vibrant-lights_23-2149836930.jpg",
"boxes": [{"xmin": 215, "ymin": 637, "xmax": 411, "ymax": 855}],
},
{
"image": "examples/close-up-coffee-latte-wooden-table_23-2147893063.jpg",
"boxes": [{"xmin": 255, "ymin": 456, "xmax": 1080, "ymax": 1064}],
},
{
"image": "examples/empty-chair-with-vase-plant_74190-2078.jpg",
"boxes": [{"xmin": 35, "ymin": 320, "xmax": 383, "ymax": 983}],
},
]
ex = gr.Examples(
examples=examples,
inputs=[annotator],
outputs=[oimg],
fn=process_bbox,
cache_examples=True,
)
demo.queue(max_size=30, api_open=False)
demo.launch(show_api=False)