feature-editor / app_cloud_gpu_robust.py
Your Name
Implement cloud GPU processing for image editing, adding support for multiple GPU Spaces (InstructPix2Pix and SD-XL Turbo) with automatic fallback. Refactor image processing function to handle requests and responses from these services, enhancing user experience with improved error handling and UI updates.
37370f7
import os
import gradio as gr
import requests
from PIL import Image
import numpy as np
import io
import json
import base64
import time
import random
# Global variables
FEATURE_TYPES = ["Eyes", "Nose", "Lips", "Face Shape", "Hair", "Body"]
MODIFICATION_PRESETS = {
"Eyes": ["Larger", "Smaller", "Change Color", "Change Shape"],
"Nose": ["Refine", "Reshape", "Resize"],
"Lips": ["Fuller", "Thinner", "Change Color"],
"Face Shape": ["Slim", "Round", "Define Jawline", "Soften Features"],
"Hair": ["Change Color", "Change Style", "Add Volume"],
"Body": ["Slim", "Athletic", "Curvy", "Muscular"]
}
# Mapping from our UI controls to text instructions
INSTRUCTION_MAPPING = {
"Eyes": {
"Larger": "make the eyes larger",
"Smaller": "make the eyes smaller",
"Change Color": "change the eye color to blue",
"Change Shape": "make the eyes more almond shaped"
},
"Nose": {
"Refine": "refine the nose shape",
"Reshape": "make the nose more straight",
"Resize": "make the nose smaller"
},
"Lips": {
"Fuller": "make the lips fuller",
"Thinner": "make the lips thinner",
"Change Color": "make the lips more red"
},
"Face Shape": {
"Slim": "make the face slimmer",
"Round": "make the face more round",
"Define Jawline": "define the jawline more",
"Soften Features": "soften the facial features"
},
"Hair": {
"Change Color": "change the hair color to blonde",
"Change Style": "make the hair wavy",
"Add Volume": "add more volume to the hair"
},
"Body": {
"Slim": "make the body slimmer",
"Athletic": "make the body more athletic",
"Curvy": "make the body more curvy",
"Muscular": "make the body more muscular"
}
}
# List of available GPU Spaces for image editing
GPU_SPACES = [
{
"name": "InstructPix2Pix",
"url": "https://timbrooks-instruct-pix2pix.hf.space/api/predict",
"format_request": lambda img_str, instruction: {
"data": [
f"data:image/png;base64,{img_str}", # Input image
instruction, # Instruction
50, # Steps
7.5, # Text CFG
1.5, # Image CFG
random.randint(1, 9999), # Random Seed
False, # Randomize seed
True, # Fix CFG
False # Randomize CFG
]
},
"parse_response": lambda response: {
"success": response.status_code == 200,
"data": response.json()["data"][0] if response.status_code == 200 and "data" in response.json() and len(response.json()["data"]) > 0 else None
}
},
{
"name": "SD-XL Turbo",
"url": "https://fffiloni-sdxl-turbo.hf.space/api/predict",
"format_request": lambda img_str, instruction: {
"data": [
f"data:image/png;base64,{img_str}", # Input image
instruction, # Prompt
"", # Negative prompt
25, # Steps
1024, # Width
1024, # Height
1.0, # Guidance scale
0.5, # Strength
random.randint(1, 9999), # Seed
]
},
"parse_response": lambda response: {
"success": response.status_code == 200,
"data": response.json()["data"][0] if response.status_code == 200 and "data" in response.json() and len(response.json()["data"]) > 0 else None
}
}
]
# Function to process image using cloud GPU services with fallback
def process_with_cloud_gpu(image, feature_type, modification_type, intensity, custom_prompt="", use_custom_prompt=False):
if image is None:
return None, "Please upload an image first."
# Prepare the instruction
if use_custom_prompt and custom_prompt:
instruction = custom_prompt
else:
instruction = INSTRUCTION_MAPPING[feature_type][modification_type]
# Adjust instruction based on intensity
if intensity < 0.3:
instruction = "slightly " + instruction
elif intensity > 0.7:
instruction = "dramatically " + instruction
# Convert image to base64 for API request
if isinstance(image, np.ndarray):
image_pil = Image.fromarray(image)
else:
image_pil = image
# Resize image if too large (most models work best with images around 512-1024px)
width, height = image_pil.size
max_dim = 1024
if width > max_dim or height > max_dim:
if width > height:
new_width = max_dim
new_height = int(height * (max_dim / width))
else:
new_height = max_dim
new_width = int(width * (max_dim / height))
image_pil = image_pil.resize((new_width, new_height), Image.LANCZOS)
# Convert to bytes for API request
buffered = io.BytesIO()
image_pil.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
# Try each GPU Space in order until one succeeds
errors = []
for space in GPU_SPACES:
try:
# Format the request according to this space's requirements
payload = space["format_request"](img_str, instruction)
# Send request with timeout
response = requests.post(space["url"], json=payload, timeout=60)
# Parse the response
result = space["parse_response"](response)
if result["success"] and result["data"]:
# Handle base64 encoded image
if isinstance(result["data"], str) and result["data"].startswith('data:image'):
# Extract the output image
image_data = result["data"].split(',')[1]
decoded_image = base64.b64decode(image_data)
output_image = Image.open(io.BytesIO(decoded_image))
return output_image, f"Edit completed successfully using {space['name']} with instruction: '{instruction}'"
# If we get here, this space didn't work
errors.append(f"{space['name']}: {response.status_code} - {response.text[:100]}...")
except Exception as e:
errors.append(f"{space['name']}: {str(e)}")
continue
# If all spaces failed, return the original image and error details
error_msg = "All GPU Spaces failed. Details:\n" + "\n".join(errors)
return image, error_msg
# UI Components
def create_ui():
with gr.Blocks(title="AI-Powered Facial & Body Feature Editor") as app:
gr.Markdown("# AI-Powered Facial & Body Feature Editor")
gr.Markdown("Upload an image and use the controls to edit specific facial and body features using cloud GPU processing.")
with gr.Row():
with gr.Column(scale=1):
# Input controls
input_image = gr.Image(label="Upload Image", type="pil")
with gr.Group():
gr.Markdown("### Feature Selection")
feature_type = gr.Dropdown(
choices=FEATURE_TYPES,
label="Select Feature",
value="Eyes"
)
# Initialize with choices for the default feature (Eyes)
modification_type = gr.Dropdown(
choices=MODIFICATION_PRESETS["Eyes"],
label="Modification Type",
value="Larger"
)
intensity = gr.Slider(
minimum=0.1,
maximum=1.0,
value=0.5,
step=0.1,
label="Intensity"
)
with gr.Group():
gr.Markdown("### Custom Prompt (Advanced)")
use_custom_prompt = gr.Checkbox(
label="Use Custom Prompt",
value=False
)
custom_prompt = gr.Textbox(
label="Custom Prompt",
placeholder="e.g., make the eyes blue and add long eyelashes"
)
edit_button = gr.Button("Apply Edit", variant="primary")
reset_button = gr.Button("Reset")
status_text = gr.Textbox(label="Status", interactive=False)
with gr.Column(scale=1):
# Output display
output_image = gr.Image(label="Edited Image", type="pil")
with gr.Accordion("Edit History", open=False):
edit_history = gr.State([])
history_gallery = gr.Gallery(label="Previous Edits")
# Information about cloud processing
with gr.Accordion("Cloud GPU Processing Information", open=True):
gr.Markdown("""
### About Cloud GPU Processing
This application uses multiple public GPU-accelerated Spaces on Hugging Face to process your images:
1. **InstructPix2Pix** - For natural language guided image editing
2. **SD-XL Turbo** - For fast, high-quality image modifications
The application will automatically try each service in order until one succeeds.
**Benefits:**
- GPU-accelerated processing without local setup
- Automatic fallback if one service is unavailable
- Works on any device with internet access
**How it works:**
1. Your image is sent to a GPU-accelerated Space
2. Your feature selections are converted to text instructions
3. The Space processes your image using GPU acceleration
4. The edited image is returned to this interface
**Note:** Processing may take 10-30 seconds depending on server load.
""")
# Event handlers
def update_modification_choices(feature):
return gr.Dropdown(choices=MODIFICATION_PRESETS[feature])
feature_type.change(
fn=update_modification_choices,
inputs=feature_type,
outputs=modification_type
)
edit_button.click(
fn=process_with_cloud_gpu,
inputs=[
input_image,
feature_type,
modification_type,
intensity,
custom_prompt,
use_custom_prompt
],
outputs=[output_image, status_text]
)
def reset_image():
return None, "Image reset."
reset_button.click(
fn=reset_image,
inputs=[],
outputs=[output_image, status_text]
)
# Add ethical usage notice
gr.Markdown("""
## Ethical Usage Notice
This tool is designed for creative and personal use. Please ensure:
- You have appropriate rights to edit the images you upload
- You use this tool responsibly and respect the dignity of individuals
- You understand that AI-generated modifications are artificial and may not represent reality
By using this application, you agree to these terms.
""")
return app
# Launch the app
if __name__ == "__main__":
app = create_ui()
app.launch(server_name="0.0.0.0", share=False)