feature-editor / app_dual_mode.py
Your Name
Implement dual processing modes for image editing, allowing users to choose between Local CPU and Remote GPU. Enhance the UI with processing mode selection and server connection checks, and update instructions for using the local server.
a8d5f56
import os
import gradio as gr
import torch
import requests
from PIL import Image
import numpy as np
import io
import json
from models.ledits_model import LEDITSModel
from utils.image_processing import preprocess_image, postprocess_image
from utils.feature_detection import detect_features, create_mask
# 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"]
}
# Initialize models
def initialize_models():
ledits_model = LEDITSModel()
return ledits_model
# Local CPU processing function
def edit_image_cpu(image, feature_type, modification_type, intensity,
num_inference_steps, guidance_scale, resolution,
custom_prompt="", use_custom_prompt=False):
if image is None:
return None, "Please upload an image first."
try:
# Convert to numpy array if needed
if isinstance(image, Image.Image):
image_np = np.array(image)
else:
image_np = image
# Resize image based on resolution setting
if resolution != "Original":
max_dim = int(resolution.split("x")[0])
height, width = image_np.shape[:2]
if height > width:
new_height = min(max_dim, height)
new_width = int(width * (new_height / height))
else:
new_width = min(max_dim, width)
new_height = int(height * (new_width / width))
image_np = Image.fromarray(image_np).resize((new_width, new_height), Image.LANCZOS)
image_np = np.array(image_np)
# Preprocess image
processed_image = preprocess_image(image_np)
# Detect features and create mask
features = detect_features(processed_image)
mask = create_mask(processed_image, feature_type, features)
# Get model
ledits_model = initialize_models()
# Prepare prompt
if use_custom_prompt and custom_prompt:
prompt = custom_prompt
else:
prompt = f"{feature_type} {modification_type}"
# Apply edit with custom parameters
edited_image = ledits_model.edit_image(
processed_image,
mask,
prompt,
intensity=intensity,
guidance_scale=guidance_scale,
num_inference_steps=num_inference_steps
)
# Postprocess
final_image = postprocess_image(edited_image, processed_image, mask)
return final_image, "Edit completed successfully."
except Exception as e:
import traceback
traceback.print_exc()
return image, f"Error during editing: {str(e)}"
# Remote GPU processing function
def process_with_local_server(image, feature_type, modification_type, intensity,
num_inference_steps, guidance_scale, resolution,
custom_prompt="", use_custom_prompt=False, server_url=None):
if image is None:
return None, "Please upload an image first."
if server_url is None or server_url == "":
return image, "Error: Local server URL not provided. Please enter your local server URL."
try:
# Ensure server URL ends with /api/predict/
if not server_url.endswith("/"):
server_url += "/"
if not server_url.endswith("api/predict/"):
server_url += "api/predict/"
# Convert image to bytes
if isinstance(image, np.ndarray):
image_pil = Image.fromarray(image)
else:
image_pil = image
img_byte_arr = io.BytesIO()
image_pil.save(img_byte_arr, format='PNG')
img_byte_arr.seek(0)
# Prepare the request data
files = {
'input_image': ('image.png', img_byte_arr, 'image/png')
}
data = {
'feature_type': feature_type,
'modification_type': modification_type,
'intensity': str(intensity),
'num_inference_steps': str(num_inference_steps),
'guidance_scale': str(guidance_scale),
'resolution': resolution,
'custom_prompt': custom_prompt,
'use_custom_prompt': str(use_custom_prompt).lower()
}
# Send request to local server
response = requests.post(server_url, files=files, data=data)
if response.status_code == 200:
# Parse the response
result = response.json()
# Get the output image
if 'data' in result and len(result['data']) >= 1:
output_data = result['data'][0]
if isinstance(output_data, str) and output_data.startswith('data:image'):
# Handle base64 encoded image
import base64
image_data = output_data.split(',')[1]
decoded_image = base64.b64decode(image_data)
output_image = Image.open(io.BytesIO(decoded_image))
return output_image, "Edit completed successfully."
# If we couldn't parse the image from the response
return image, f"Error: Could not parse response from local server."
else:
return image, f"Error: Local server returned status code {response.status_code}."
except Exception as e:
return image, f"Error connecting to local server: {str(e)}"
# Combined processing function that chooses between CPU and GPU
def process_image(image, feature_type, modification_type, intensity,
num_inference_steps, guidance_scale, resolution,
custom_prompt, use_custom_prompt, processing_mode, server_url):
if processing_mode == "Local CPU":
return edit_image_cpu(
image, feature_type, modification_type, intensity,
num_inference_steps, guidance_scale, resolution,
custom_prompt, use_custom_prompt
)
else: # "Remote GPU"
return process_with_local_server(
image, feature_type, modification_type, intensity,
num_inference_steps, guidance_scale, resolution,
custom_prompt, use_custom_prompt, server_url
)
# 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.")
# Processing mode selection
with gr.Group():
gr.Markdown("### Processing Mode")
processing_mode = gr.Radio(
choices=["Local CPU", "Remote GPU"],
label="Select Processing Mode",
value="Local CPU",
info="Choose 'Local CPU' to use Hugging Face's CPU or 'Remote GPU' to use your local GPU server"
)
# Server connection (only visible in Remote GPU mode)
with gr.Group(visible=False) as server_group:
gr.Markdown("### Local GPU Server Connection")
server_url = gr.Textbox(
label="Local Server URL",
placeholder="Enter the URL of your local GPU server (e.g., https://12345.gradio.app)",
value=""
)
server_status = gr.Textbox(label="Server Status", value="Not connected", interactive=False)
def check_server(url):
if not url:
return "Not connected"
try:
# Ensure URL ends with /
if not url.endswith("/"):
url += "/"
# Try to connect to the server
response = requests.get(url)
if response.status_code == 200:
return "Connected successfully"
else:
return f"Error: Server returned status code {response.status_code}"
except Exception as e:
return f"Error connecting to server: {str(e)}"
check_button = gr.Button("Check Connection")
check_button.click(fn=check_server, inputs=server_url, outputs=server_status)
# Show/hide server connection based on processing mode
def toggle_server_group(mode):
return gr.Group(visible=(mode == "Remote GPU"))
processing_mode.change(
fn=toggle_server_group,
inputs=processing_mode,
outputs=server_group
)
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., blue eyes with long eyelashes"
)
with gr.Group():
gr.Markdown("### Performance Settings")
num_inference_steps = gr.Slider(
minimum=5,
maximum=50,
value=20,
step=1,
label="Inference Steps (lower = faster, higher = better quality)"
)
guidance_scale = gr.Slider(
minimum=1.0,
maximum=15.0,
value=7.5,
step=0.5,
label="Guidance Scale (lower = more creative, higher = more accurate)"
)
resolution = gr.Dropdown(
choices=["Original", "512x512", "768x768", "1024x1024"],
label="Processing Resolution",
value="512x512"
)
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")
# 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_image,
inputs=[
input_image,
feature_type,
modification_type,
intensity,
num_inference_steps,
guidance_scale,
resolution,
custom_prompt,
use_custom_prompt,
processing_mode,
server_url
],
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.
""")
# Add dual-mode instructions
gr.Markdown("""
## Processing Modes
This application supports two processing modes:
### 1. Local CPU Mode
- Uses Hugging Face's CPU for processing
- Works immediately without additional setup
- Slower processing (may take several minutes per edit)
- No additional software required
### 2. Remote GPU Mode
- Uses your local computer's GPU for processing
- Requires setting up the local server component
- Much faster processing (typically seconds per edit)
- Requires PyTorch and other dependencies installed locally
To use Remote GPU Mode:
1. Download and run the local_server.py file on your computer
2. The server will provide a URL (copy this URL)
3. Select "Remote GPU" mode above
4. Paste the URL into the "Local Server URL" field
5. Click "Check Connection" to verify
""")
return app
# Launch the app
if __name__ == "__main__":
app = create_ui()
app.launch(server_name="0.0.0.0", share=False)