|
import gradio as gr |
|
from gradio_image_annotation import image_annotator |
|
import fal_client |
|
from PIL import Image |
|
import io |
|
import base64 |
|
import numpy as np |
|
import os |
|
|
|
def process_images(annotated_image, second_image, user_api_key=None, progress=gr.Progress()): |
|
""" |
|
Process the annotated image and second image using fal API |
|
""" |
|
|
|
if annotated_image is None: |
|
return None, "Please provide the first image and draw an annotation box" |
|
|
|
|
|
if second_image is None or (isinstance(second_image, np.ndarray) and second_image.size == 0): |
|
return None, "Please provide the second image" |
|
|
|
|
|
if not annotated_image.get("boxes") or len(annotated_image["boxes"]) == 0: |
|
return None, "Please draw an annotation box on the first image" |
|
|
|
|
|
box = annotated_image["boxes"][0] |
|
xmin = box.get("xmin") |
|
ymin = box.get("ymin") |
|
xmax = box.get("xmax") |
|
ymax = box.get("ymax") |
|
|
|
|
|
prompt = f"""add the <central object in the second image> in the first image only inside an imaginary box defined by pixels values "xmin": {xmin}, "ymin": {ymin}, "xmax": {xmax}, "ymax": {ymax}. Take care of shadows, lighting, style, and general concept of objects as per the first image.""" |
|
|
|
progress(0.2, desc="Gradio is preparing your images...") |
|
|
|
try: |
|
|
|
original_key = os.environ.get("FAL_KEY", "") |
|
|
|
if user_api_key and user_api_key.strip(): |
|
|
|
os.environ["FAL_KEY"] = user_api_key.strip() |
|
api_key_source = "user-provided" |
|
elif original_key: |
|
|
|
api_key_source = "environment" |
|
else: |
|
|
|
return None, "โ ๏ธ No FAL API key found. Please either:\n1. Duplicate this app and set your FAL_KEY as a secret, or\n2. Enter your FAL API key in the field provided above." |
|
|
|
|
|
first_img = annotated_image["image"] |
|
if isinstance(first_img, np.ndarray): |
|
|
|
first_img_pil = Image.fromarray(first_img.astype('uint8')) |
|
|
|
img1_bytes = io.BytesIO() |
|
first_img_pil.save(img1_bytes, format='PNG') |
|
img1_bytes.seek(0) |
|
uploaded_file1 = fal_client.upload(img1_bytes.getvalue(), "image/png") |
|
elif isinstance(first_img, str): |
|
|
|
uploaded_file1 = fal_client.upload_file(first_img) |
|
else: |
|
|
|
img1_bytes = io.BytesIO() |
|
first_img.save(img1_bytes, format='PNG') |
|
img1_bytes.seek(0) |
|
uploaded_file1 = fal_client.upload(img1_bytes.getvalue(), "image/png") |
|
|
|
|
|
if isinstance(second_image, np.ndarray): |
|
second_img_pil = Image.fromarray(second_image.astype('uint8')) |
|
img2_bytes = io.BytesIO() |
|
second_img_pil.save(img2_bytes, format='PNG') |
|
img2_bytes.seek(0) |
|
uploaded_file2 = fal_client.upload(img2_bytes.getvalue(), "image/png") |
|
elif isinstance(second_image, str): |
|
uploaded_file2 = fal_client.upload_file(second_image) |
|
else: |
|
img2_bytes = io.BytesIO() |
|
second_image.save(img2_bytes, format='PNG') |
|
img2_bytes.seek(0) |
|
uploaded_file2 = fal_client.upload(img2_bytes.getvalue(), "image/png") |
|
|
|
progress(0.4, desc="Processing with nano-banana...") |
|
|
|
|
|
def on_queue_update(update): |
|
if isinstance(update, fal_client.InProgress): |
|
|
|
progress(0.6, desc="nano-banana is working on your image...") |
|
|
|
if hasattr(update, 'logs') and update.logs: |
|
for log in update.logs: |
|
print(log.get("message", "")) |
|
|
|
|
|
result = fal_client.subscribe( |
|
"fal-ai/nano-banana/edit", |
|
arguments={ |
|
"prompt": prompt, |
|
"image_urls": [f"{uploaded_file1}", f"{uploaded_file2}"] |
|
}, |
|
with_logs=True, |
|
on_queue_update=on_queue_update, |
|
) |
|
|
|
progress(0.95, desc="Finalizing...") |
|
|
|
|
|
if result and "images" in result and len(result["images"]) > 0: |
|
output_url = result["images"][0]["url"] |
|
description = result.get("description", "Image processed successfully!") |
|
progress(1.0, desc="Complete!") |
|
return output_url, description |
|
else: |
|
return None, "Failed to generate image. Please check your API key or try again." |
|
|
|
except Exception as e: |
|
error_message = str(e).lower() |
|
|
|
|
|
if "401" in error_message or "unauthorized" in error_message or "api key" in error_message: |
|
return None, f"โ ๏ธ API Authentication Error: Invalid or missing FAL API key.\n\nPlease either:\n1. Duplicate this app and set your FAL_KEY as a secret, or\n2. Enter your valid FAL API key in the field provided above.\n\nGet your API key at: https://fal.ai" |
|
|
|
|
|
elif "429" in error_message or "rate limit" in error_message: |
|
return None, "โ ๏ธ Rate limit exceeded. Please wait a moment and try again, or use your own API key for higher limits." |
|
|
|
|
|
elif "500" in error_message or "502" in error_message or "503" in error_message: |
|
return None, f"โ ๏ธ FAL API server error. The service might be temporarily unavailable.\n\nPlease either:\n1. Try again in a few moments, or\n2. Use your own API key by entering it in the field above.\n\nError details: {str(e)}" |
|
|
|
|
|
else: |
|
return None, f"โ ๏ธ Error occurred: {str(e)}\n\nIf the error persists, please either:\n1. Duplicate this app and set your FAL_KEY as a secret, or\n2. Enter your FAL API key in the field provided above.\n\nGet your API key at: https://fal.ai" |
|
|
|
finally: |
|
|
|
if user_api_key and user_api_key.strip(): |
|
if original_key: |
|
os.environ["FAL_KEY"] = original_key |
|
else: |
|
os.environ.pop("FAL_KEY", None) |
|
|
|
|
|
|
|
with gr.Blocks(theme='ocean') as demo: |
|
|
|
navbar = gr.Navbar( |
|
value=[ |
|
("Documentation", "https://docs.fal.ai"), |
|
("FAL.AI nano-banana", "https://fal.ai/models/fal-ai/nano-banana/edit/api"), |
|
("Learn more about Gradio Navbar", "https://www.gradio.app/guides/multipage-apps#customizing-the-navbar") |
|
], |
|
visible=True, |
|
main_page_name="๐จ guided nano banana" |
|
) |
|
|
|
gr.HTML( |
|
""" |
|
<h1><center>Guide Your Nano Banana๐๐</center></h1> |
|
|
|
<b>How to use:</b><br> |
|
1. Upload or capture the first image and draw a box where you want to place an object<br> |
|
2. Upload the second image containing the object you want to insert<br> |
|
3. Click "Generate Composite Image" and wait for the Gradio and Nano-Banana to blend the images<br> |
|
|
|
The Gradio app will intelligently place the object from the second image into the boxed area of the first image, |
|
taking care of lighting, shadows, and proper integration. |
|
""" |
|
) |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
with gr.Accordion("๐ API Configuration (Optional)", open=False): |
|
gr.Markdown( |
|
""" |
|
**Note:** If you're experiencing API errors or want to use your own FAL account: |
|
- Enter your FAL API key below, or |
|
- [Duplicate this Space](https://huggingface.co/spaces) and set FAL_KEY as a secret |
|
- Get your API key at [fal.ai](https://fal.ai) |
|
""" |
|
) |
|
api_key_input = gr.Textbox( |
|
label="FAL API Key", |
|
placeholder="Enter your FAL key (optional)", |
|
type="password", |
|
interactive=True, |
|
info="Your key will be used only for this session and won't be stored" |
|
) |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
gr.Markdown("### Step 1: Annotate First Image") |
|
|
|
from gradio_image_annotation import image_annotator |
|
|
|
first_image = image_annotator( |
|
value=None, |
|
label="Draw a box where you want to place the object", |
|
image_type="pil", |
|
single_box=True, |
|
disable_edit_boxes=True, |
|
show_download_button=False, |
|
show_share_button=False, |
|
box_thickness=3, |
|
box_selected_thickness=4, |
|
show_label=True, |
|
|
|
|
|
) |
|
|
|
with gr.Column(scale=1): |
|
gr.Markdown("### Step 2: Upload Second Image") |
|
|
|
second_image = gr.Image( |
|
label="Image containing the object to insert", |
|
type="numpy", |
|
height=400, |
|
) |
|
|
|
generate_btn = gr.Button("Step 3: ๐ Generate Composite Image", variant="primary", size="lg") |
|
|
|
|
|
with gr.Column(): |
|
output_image = gr.Image( |
|
label="Generated Composite Image", |
|
type="filepath", |
|
height=500, |
|
) |
|
status_text = gr.Textbox( |
|
label="Status", |
|
placeholder="Results will appear here...", |
|
lines=3, |
|
) |
|
|
|
|
|
generate_btn.click( |
|
fn=process_images, |
|
inputs=[first_image, second_image, api_key_input], |
|
outputs=[output_image, status_text], |
|
show_progress=True, |
|
) |
|
|
|
with demo.route("Tips", "/tips"): |
|
gr.Markdown( |
|
""" |
|
# โน๏ธ Tips for Best Results |
|
- **Box Placement**: Draw the box exactly where you want the object to appear |
|
- **Image Quality**: Use high-resolution images for better results |
|
- **Object Selection**: The second image should clearly show the object you want to insert |
|
- **Lighting**: Images with similar lighting conditions work best |
|
- **Processing Time**: Generation typically takes 10-30 seconds |
|
- **API Key**: If you encounter errors, try using your own FAL API key |
|
""" |
|
) |
|
|
|
|
|
navbar = gr.Navbar( |
|
visible=True, |
|
main_page_name="Home", |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.launch(ssr_mode=False) |