File size: 12,551 Bytes
37370f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
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)