Your Name commited on
Commit
241b467
·
1 Parent(s): a8d5f56

Add additional entries to .gitignore for Python environments, cache files, distribution artifacts, IDE settings, and local development logs

Browse files
Files changed (4) hide show
  1. .gitignore +25 -0
  2. CLOUD_GPU_GUIDE.md +84 -0
  3. app.py +113 -235
  4. app_cloud_gpu.py +269 -0
.gitignore CHANGED
@@ -1 +1,26 @@
1
  image-edit-app-dual-mode.zip
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  image-edit-app-dual-mode.zip
2
+ image-edit-app-cloud-gpu.zip
3
+
4
+ # Python virtual environments
5
+ pytorch_env/
6
+ venv/
7
+ env/
8
+ .env/
9
+
10
+ # Python cache files
11
+ __pycache__/
12
+ *.py[cod]
13
+ *$py.class
14
+
15
+ # Distribution / packaging
16
+ dist/
17
+ build/
18
+ *.egg-info/
19
+
20
+ # IDE settings
21
+ .vscode/
22
+ .idea/
23
+
24
+ # Local development settings
25
+ *.log
26
+ .env
CLOUD_GPU_GUIDE.md ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cloud GPU Integration Guide
2
+
3
+ This document provides instructions for using the cloud-based GPU version of the AI-Powered Facial and Body Feature Editor, which leverages InstructPix2Pix for GPU-accelerated processing without requiring any local setup.
4
+
5
+ ## Overview
6
+
7
+ The cloud-based GPU version of the application uses InstructPix2Pix, a public GPU-accelerated Space on Hugging Face, to process your images. This approach offers several benefits:
8
+
9
+ - GPU-accelerated processing without local setup
10
+ - Works on any device with internet access
11
+ - No need to install CUDA or PyTorch
12
+ - Simpler deployment and maintenance
13
+
14
+ ## How It Works
15
+
16
+ 1. Your image is sent to the InstructPix2Pix Space
17
+ 2. Your feature selections are converted to text instructions
18
+ 3. The Space processes your image using GPU acceleration
19
+ 4. The edited image is returned to the interface
20
+
21
+ ## Setup Instructions
22
+
23
+ 1. Download the `app_cloud_gpu.py` file
24
+ 2. Replace your current `app.py` file in your Hugging Face Space with this file
25
+ 3. Commit and push the changes to your Space
26
+ 4. Your Space will automatically rebuild with the cloud GPU integration
27
+
28
+ ```bash
29
+ # In your local repository
30
+ cp app_cloud_gpu.py app.py
31
+ git add app.py
32
+ git commit -m "Implement cloud GPU integration with InstructPix2Pix"
33
+ git push
34
+ ```
35
+
36
+ ## Feature Mapping
37
+
38
+ The application maps your feature selections to text instructions that InstructPix2Pix can understand:
39
+
40
+ | Feature Type | Modification Type | Instruction |
41
+ |--------------|-------------------|-------------|
42
+ | Eyes | Larger | "make the eyes larger" |
43
+ | Eyes | Smaller | "make the eyes smaller" |
44
+ | Face Shape | Slim | "make the face slimmer" |
45
+ | Lips | Fuller | "make the lips fuller" |
46
+ | ... | ... | ... |
47
+
48
+ The intensity slider modifies these instructions:
49
+ - Low intensity (0.1-0.3): Adds "slightly" to the instruction
50
+ - Medium intensity (0.4-0.7): Uses the base instruction
51
+ - High intensity (0.8-1.0): Adds "dramatically" to the instruction
52
+
53
+ ## Custom Prompts
54
+
55
+ You can also use custom prompts for more specific edits. Enable the "Use Custom Prompt" checkbox and enter your desired instruction, such as:
56
+ - "make the eyes blue and add long eyelashes"
57
+ - "add a subtle smile"
58
+ - "make the hair curly and blonde"
59
+
60
+ ## Performance Considerations
61
+
62
+ - Processing typically takes 10-30 seconds depending on server load
63
+ - The InstructPix2Pix Space may have usage limits or queues
64
+ - Images are automatically resized to 512x512 pixels for optimal processing
65
+
66
+ ## Troubleshooting
67
+
68
+ If you encounter issues:
69
+
70
+ 1. **Connection errors**: The InstructPix2Pix Space might be temporarily unavailable. Try again later.
71
+ 2. **Processing errors**: Try a different image or a simpler edit instruction.
72
+ 3. **Unexpected results**: Adjust your instruction or try using a custom prompt for more control.
73
+
74
+ ## Limitations
75
+
76
+ - Less precise control compared to direct feature manipulation
77
+ - Results depend on how well InstructPix2Pix understands the instructions
78
+ - Subject to the availability of the public InstructPix2Pix Space
79
+
80
+ ## Future Improvements
81
+
82
+ - Add support for additional public GPU Spaces
83
+ - Implement a fallback mechanism if InstructPix2Pix is unavailable
84
+ - Expand the instruction mapping for more specific edits
app.py CHANGED
@@ -1,14 +1,11 @@
1
  import os
2
  import gradio as gr
3
- import torch
4
  import requests
5
  from PIL import Image
6
  import numpy as np
7
  import io
8
  import json
9
- from models.ledits_model import LEDITSModel
10
- from utils.image_processing import preprocess_image, postprocess_image
11
- from utils.feature_detection import detect_features, create_mask
12
 
13
  # Global variables
14
  FEATURE_TYPES = ["Eyes", "Nose", "Lips", "Face Shape", "Hair", "Body"]
@@ -21,218 +18,131 @@ MODIFICATION_PRESETS = {
21
  "Body": ["Slim", "Athletic", "Curvy", "Muscular"]
22
  }
23
 
24
- # Initialize models
25
- def initialize_models():
26
- ledits_model = LEDITSModel()
27
- return ledits_model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- # Local CPU processing function
30
- def edit_image_cpu(image, feature_type, modification_type, intensity,
31
- num_inference_steps, guidance_scale, resolution,
32
- custom_prompt="", use_custom_prompt=False):
33
  if image is None:
34
  return None, "Please upload an image first."
35
 
36
  try:
37
- # Convert to numpy array if needed
38
- if isinstance(image, Image.Image):
39
- image_np = np.array(image)
40
- else:
41
- image_np = image
42
-
43
- # Resize image based on resolution setting
44
- if resolution != "Original":
45
- max_dim = int(resolution.split("x")[0])
46
- height, width = image_np.shape[:2]
47
-
48
- if height > width:
49
- new_height = min(max_dim, height)
50
- new_width = int(width * (new_height / height))
51
- else:
52
- new_width = min(max_dim, width)
53
- new_height = int(height * (new_width / width))
54
-
55
- image_np = Image.fromarray(image_np).resize((new_width, new_height), Image.LANCZOS)
56
- image_np = np.array(image_np)
57
-
58
- # Preprocess image
59
- processed_image = preprocess_image(image_np)
60
-
61
- # Detect features and create mask
62
- features = detect_features(processed_image)
63
- mask = create_mask(processed_image, feature_type, features)
64
-
65
- # Get model
66
- ledits_model = initialize_models()
67
-
68
- # Prepare prompt
69
  if use_custom_prompt and custom_prompt:
70
- prompt = custom_prompt
71
  else:
72
- prompt = f"{feature_type} {modification_type}"
73
-
74
- # Apply edit with custom parameters
75
- edited_image = ledits_model.edit_image(
76
- processed_image,
77
- mask,
78
- prompt,
79
- intensity=intensity,
80
- guidance_scale=guidance_scale,
81
- num_inference_steps=num_inference_steps
82
- )
83
-
84
- # Postprocess
85
- final_image = postprocess_image(edited_image, processed_image, mask)
86
-
87
- return final_image, "Edit completed successfully."
88
-
89
- except Exception as e:
90
- import traceback
91
- traceback.print_exc()
92
- return image, f"Error during editing: {str(e)}"
93
-
94
- # Remote GPU processing function
95
- def process_with_local_server(image, feature_type, modification_type, intensity,
96
- num_inference_steps, guidance_scale, resolution,
97
- custom_prompt="", use_custom_prompt=False, server_url=None):
98
- if image is None:
99
- return None, "Please upload an image first."
100
-
101
- if server_url is None or server_url == "":
102
- return image, "Error: Local server URL not provided. Please enter your local server URL."
103
-
104
- try:
105
- # Ensure server URL ends with /api/predict/
106
- if not server_url.endswith("/"):
107
- server_url += "/"
108
- if not server_url.endswith("api/predict/"):
109
- server_url += "api/predict/"
110
 
111
- # Convert image to bytes
112
  if isinstance(image, np.ndarray):
113
  image_pil = Image.fromarray(image)
114
  else:
115
  image_pil = image
116
 
117
- img_byte_arr = io.BytesIO()
118
- image_pil.save(img_byte_arr, format='PNG')
119
- img_byte_arr.seek(0)
 
 
 
 
 
 
 
 
120
 
121
- # Prepare the request data
122
- files = {
123
- 'input_image': ('image.png', img_byte_arr, 'image/png')
124
- }
125
 
126
- data = {
127
- 'feature_type': feature_type,
128
- 'modification_type': modification_type,
129
- 'intensity': str(intensity),
130
- 'num_inference_steps': str(num_inference_steps),
131
- 'guidance_scale': str(guidance_scale),
132
- 'resolution': resolution,
133
- 'custom_prompt': custom_prompt,
134
- 'use_custom_prompt': str(use_custom_prompt).lower()
 
 
 
 
 
135
  }
136
 
137
- # Send request to local server
138
- response = requests.post(server_url, files=files, data=data)
139
 
140
  if response.status_code == 200:
141
- # Parse the response
142
  result = response.json()
143
 
144
- # Get the output image
145
  if 'data' in result and len(result['data']) >= 1:
146
  output_data = result['data'][0]
147
  if isinstance(output_data, str) and output_data.startswith('data:image'):
148
  # Handle base64 encoded image
149
- import base64
150
  image_data = output_data.split(',')[1]
151
  decoded_image = base64.b64decode(image_data)
152
  output_image = Image.open(io.BytesIO(decoded_image))
153
- return output_image, "Edit completed successfully."
154
 
155
  # If we couldn't parse the image from the response
156
- return image, f"Error: Could not parse response from local server."
157
  else:
158
- return image, f"Error: Local server returned status code {response.status_code}."
159
 
160
  except Exception as e:
161
- return image, f"Error connecting to local server: {str(e)}"
162
-
163
- # Combined processing function that chooses between CPU and GPU
164
- def process_image(image, feature_type, modification_type, intensity,
165
- num_inference_steps, guidance_scale, resolution,
166
- custom_prompt, use_custom_prompt, processing_mode, server_url):
167
- if processing_mode == "Local CPU":
168
- return edit_image_cpu(
169
- image, feature_type, modification_type, intensity,
170
- num_inference_steps, guidance_scale, resolution,
171
- custom_prompt, use_custom_prompt
172
- )
173
- else: # "Remote GPU"
174
- return process_with_local_server(
175
- image, feature_type, modification_type, intensity,
176
- num_inference_steps, guidance_scale, resolution,
177
- custom_prompt, use_custom_prompt, server_url
178
- )
179
 
180
  # UI Components
181
  def create_ui():
182
  with gr.Blocks(title="AI-Powered Facial & Body Feature Editor") as app:
183
  gr.Markdown("# AI-Powered Facial & Body Feature Editor")
184
- gr.Markdown("Upload an image and use the controls to edit specific facial and body features.")
185
-
186
- # Processing mode selection
187
- with gr.Group():
188
- gr.Markdown("### Processing Mode")
189
- processing_mode = gr.Radio(
190
- choices=["Local CPU", "Remote GPU"],
191
- label="Select Processing Mode",
192
- value="Local CPU",
193
- info="Choose 'Local CPU' to use Hugging Face's CPU or 'Remote GPU' to use your local GPU server"
194
- )
195
-
196
- # Server connection (only visible in Remote GPU mode)
197
- with gr.Group(visible=False) as server_group:
198
- gr.Markdown("### Local GPU Server Connection")
199
- server_url = gr.Textbox(
200
- label="Local Server URL",
201
- placeholder="Enter the URL of your local GPU server (e.g., https://12345.gradio.app)",
202
- value=""
203
- )
204
- server_status = gr.Textbox(label="Server Status", value="Not connected", interactive=False)
205
-
206
- def check_server(url):
207
- if not url:
208
- return "Not connected"
209
-
210
- try:
211
- # Ensure URL ends with /
212
- if not url.endswith("/"):
213
- url += "/"
214
-
215
- # Try to connect to the server
216
- response = requests.get(url)
217
- if response.status_code == 200:
218
- return "Connected successfully"
219
- else:
220
- return f"Error: Server returned status code {response.status_code}"
221
- except Exception as e:
222
- return f"Error connecting to server: {str(e)}"
223
-
224
- check_button = gr.Button("Check Connection")
225
- check_button.click(fn=check_server, inputs=server_url, outputs=server_status)
226
-
227
- # Show/hide server connection based on processing mode
228
- def toggle_server_group(mode):
229
- return gr.Group(visible=(mode == "Remote GPU"))
230
-
231
- processing_mode.change(
232
- fn=toggle_server_group,
233
- inputs=processing_mode,
234
- outputs=server_group
235
- )
236
 
237
  with gr.Row():
238
  with gr.Column(scale=1):
@@ -270,29 +180,7 @@ def create_ui():
270
  )
271
  custom_prompt = gr.Textbox(
272
  label="Custom Prompt",
273
- placeholder="e.g., blue eyes with long eyelashes"
274
- )
275
-
276
- with gr.Group():
277
- gr.Markdown("### Performance Settings")
278
- num_inference_steps = gr.Slider(
279
- minimum=5,
280
- maximum=50,
281
- value=20,
282
- step=1,
283
- label="Inference Steps (lower = faster, higher = better quality)"
284
- )
285
- guidance_scale = gr.Slider(
286
- minimum=1.0,
287
- maximum=15.0,
288
- value=7.5,
289
- step=0.5,
290
- label="Guidance Scale (lower = more creative, higher = more accurate)"
291
- )
292
- resolution = gr.Dropdown(
293
- choices=["Original", "512x512", "768x768", "1024x1024"],
294
- label="Processing Resolution",
295
- value="512x512"
296
  )
297
 
298
  edit_button = gr.Button("Apply Edit", variant="primary")
@@ -306,6 +194,27 @@ def create_ui():
306
  with gr.Accordion("Edit History", open=False):
307
  edit_history = gr.State([])
308
  history_gallery = gr.Gallery(label="Previous Edits")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
  # Event handlers
311
  def update_modification_choices(feature):
@@ -318,19 +227,14 @@ def create_ui():
318
  )
319
 
320
  edit_button.click(
321
- fn=process_image,
322
  inputs=[
323
  input_image,
324
  feature_type,
325
  modification_type,
326
- intensity,
327
- num_inference_steps,
328
- guidance_scale,
329
- resolution,
330
  custom_prompt,
331
- use_custom_prompt,
332
- processing_mode,
333
- server_url
334
  ],
335
  outputs=[output_image, status_text]
336
  )
@@ -357,32 +261,6 @@ def create_ui():
357
  By using this application, you agree to these terms.
358
  """)
359
 
360
- # Add dual-mode instructions
361
- gr.Markdown("""
362
- ## Processing Modes
363
-
364
- This application supports two processing modes:
365
-
366
- ### 1. Local CPU Mode
367
- - Uses Hugging Face's CPU for processing
368
- - Works immediately without additional setup
369
- - Slower processing (may take several minutes per edit)
370
- - No additional software required
371
-
372
- ### 2. Remote GPU Mode
373
- - Uses your local computer's GPU for processing
374
- - Requires setting up the local server component
375
- - Much faster processing (typically seconds per edit)
376
- - Requires PyTorch and other dependencies installed locally
377
-
378
- To use Remote GPU Mode:
379
- 1. Download and run the local_server.py file on your computer
380
- 2. The server will provide a URL (copy this URL)
381
- 3. Select "Remote GPU" mode above
382
- 4. Paste the URL into the "Local Server URL" field
383
- 5. Click "Check Connection" to verify
384
- """)
385
-
386
  return app
387
 
388
  # Launch the app
 
1
  import os
2
  import gradio as gr
 
3
  import requests
4
  from PIL import Image
5
  import numpy as np
6
  import io
7
  import json
8
+ import base64
 
 
9
 
10
  # Global variables
11
  FEATURE_TYPES = ["Eyes", "Nose", "Lips", "Face Shape", "Hair", "Body"]
 
18
  "Body": ["Slim", "Athletic", "Curvy", "Muscular"]
19
  }
20
 
21
+ # Mapping from our UI controls to InstructPix2Pix instructions
22
+ INSTRUCTION_MAPPING = {
23
+ "Eyes": {
24
+ "Larger": "make the eyes larger",
25
+ "Smaller": "make the eyes smaller",
26
+ "Change Color": "change the eye color to blue",
27
+ "Change Shape": "make the eyes more almond shaped"
28
+ },
29
+ "Nose": {
30
+ "Refine": "refine the nose shape",
31
+ "Reshape": "make the nose more straight",
32
+ "Resize": "make the nose smaller"
33
+ },
34
+ "Lips": {
35
+ "Fuller": "make the lips fuller",
36
+ "Thinner": "make the lips thinner",
37
+ "Change Color": "make the lips more red"
38
+ },
39
+ "Face Shape": {
40
+ "Slim": "make the face slimmer",
41
+ "Round": "make the face more round",
42
+ "Define Jawline": "define the jawline more",
43
+ "Soften Features": "soften the facial features"
44
+ },
45
+ "Hair": {
46
+ "Change Color": "change the hair color to blonde",
47
+ "Change Style": "make the hair wavy",
48
+ "Add Volume": "add more volume to the hair"
49
+ },
50
+ "Body": {
51
+ "Slim": "make the body slimmer",
52
+ "Athletic": "make the body more athletic",
53
+ "Curvy": "make the body more curvy",
54
+ "Muscular": "make the body more muscular"
55
+ }
56
+ }
57
 
58
+ # Function to process image using InstructPix2Pix
59
+ def process_with_instructpix2pix(image, feature_type, modification_type, intensity, custom_prompt="", use_custom_prompt=False):
 
 
60
  if image is None:
61
  return None, "Please upload an image first."
62
 
63
  try:
64
+ # Prepare the instruction
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  if use_custom_prompt and custom_prompt:
66
+ instruction = custom_prompt
67
  else:
68
+ instruction = INSTRUCTION_MAPPING[feature_type][modification_type]
69
+
70
+ # Adjust instruction based on intensity
71
+ if intensity < 0.3:
72
+ instruction = "slightly " + instruction
73
+ elif intensity > 0.7:
74
+ instruction = "dramatically " + instruction
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ # Convert image to base64 for API request
77
  if isinstance(image, np.ndarray):
78
  image_pil = Image.fromarray(image)
79
  else:
80
  image_pil = image
81
 
82
+ # Resize image if too large (InstructPix2Pix works best with images around 512x512)
83
+ width, height = image_pil.size
84
+ max_dim = 512
85
+ if width > max_dim or height > max_dim:
86
+ if width > height:
87
+ new_width = max_dim
88
+ new_height = int(height * (max_dim / width))
89
+ else:
90
+ new_height = max_dim
91
+ new_width = int(width * (max_dim / height))
92
+ image_pil = image_pil.resize((new_width, new_height), Image.LANCZOS)
93
 
94
+ # Convert to bytes for API request
95
+ buffered = io.BytesIO()
96
+ image_pil.save(buffered, format="PNG")
97
+ img_str = base64.b64encode(buffered.getvalue()).decode()
98
 
99
+ # Create API request to InstructPix2Pix Space
100
+ api_url = "https://timbrooks-instruct-pix2pix.hf.space/api/predict"
101
+ payload = {
102
+ "data": [
103
+ f"data:image/png;base64,{img_str}", # Input image
104
+ instruction, # Instruction
105
+ 50, # Steps
106
+ 7.5, # Text CFG
107
+ 1.5, # Image CFG
108
+ 1371, # Seed
109
+ False, # Randomize seed
110
+ True, # Fix CFG
111
+ False # Randomize CFG
112
+ ]
113
  }
114
 
115
+ # Send request
116
+ response = requests.post(api_url, json=payload)
117
 
118
  if response.status_code == 200:
 
119
  result = response.json()
120
 
121
+ # Extract the output image
122
  if 'data' in result and len(result['data']) >= 1:
123
  output_data = result['data'][0]
124
  if isinstance(output_data, str) and output_data.startswith('data:image'):
125
  # Handle base64 encoded image
 
126
  image_data = output_data.split(',')[1]
127
  decoded_image = base64.b64decode(image_data)
128
  output_image = Image.open(io.BytesIO(decoded_image))
129
+ return output_image, f"Edit completed successfully using instruction: '{instruction}'"
130
 
131
  # If we couldn't parse the image from the response
132
+ return image, f"Error: Could not parse response from InstructPix2Pix."
133
  else:
134
+ return image, f"Error: InstructPix2Pix returned status code {response.status_code}."
135
 
136
  except Exception as e:
137
+ import traceback
138
+ traceback.print_exc()
139
+ return image, f"Error processing with InstructPix2Pix: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
  # UI Components
142
  def create_ui():
143
  with gr.Blocks(title="AI-Powered Facial & Body Feature Editor") as app:
144
  gr.Markdown("# AI-Powered Facial & Body Feature Editor")
145
+ gr.Markdown("Upload an image and use the controls to edit specific facial and body features using cloud GPU processing.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
  with gr.Row():
148
  with gr.Column(scale=1):
 
180
  )
181
  custom_prompt = gr.Textbox(
182
  label="Custom Prompt",
183
+ placeholder="e.g., make the eyes blue and add long eyelashes"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  )
185
 
186
  edit_button = gr.Button("Apply Edit", variant="primary")
 
194
  with gr.Accordion("Edit History", open=False):
195
  edit_history = gr.State([])
196
  history_gallery = gr.Gallery(label="Previous Edits")
197
+
198
+ # Information about cloud processing
199
+ with gr.Accordion("Cloud GPU Processing Information", open=True):
200
+ gr.Markdown("""
201
+ ### About Cloud GPU Processing
202
+
203
+ This application uses InstructPix2Pix, a public GPU-accelerated Space on Hugging Face, to process your images.
204
+
205
+ **Benefits:**
206
+ - GPU-accelerated processing without local setup
207
+ - Works on any device with internet access
208
+ - No need to install CUDA or PyTorch
209
+
210
+ **How it works:**
211
+ 1. Your image is sent to the InstructPix2Pix Space
212
+ 2. Your feature selections are converted to text instructions
213
+ 3. The Space processes your image using GPU acceleration
214
+ 4. The edited image is returned to this interface
215
+
216
+ **Note:** Processing may take 10-30 seconds depending on server load.
217
+ """)
218
 
219
  # Event handlers
220
  def update_modification_choices(feature):
 
227
  )
228
 
229
  edit_button.click(
230
+ fn=process_with_instructpix2pix,
231
  inputs=[
232
  input_image,
233
  feature_type,
234
  modification_type,
235
+ intensity,
 
 
 
236
  custom_prompt,
237
+ use_custom_prompt
 
 
238
  ],
239
  outputs=[output_image, status_text]
240
  )
 
261
  By using this application, you agree to these terms.
262
  """)
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  return app
265
 
266
  # Launch the app
app_cloud_gpu.py ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import requests
4
+ from PIL import Image
5
+ import numpy as np
6
+ import io
7
+ import json
8
+ import base64
9
+
10
+ # Global variables
11
+ FEATURE_TYPES = ["Eyes", "Nose", "Lips", "Face Shape", "Hair", "Body"]
12
+ MODIFICATION_PRESETS = {
13
+ "Eyes": ["Larger", "Smaller", "Change Color", "Change Shape"],
14
+ "Nose": ["Refine", "Reshape", "Resize"],
15
+ "Lips": ["Fuller", "Thinner", "Change Color"],
16
+ "Face Shape": ["Slim", "Round", "Define Jawline", "Soften Features"],
17
+ "Hair": ["Change Color", "Change Style", "Add Volume"],
18
+ "Body": ["Slim", "Athletic", "Curvy", "Muscular"]
19
+ }
20
+
21
+ # Mapping from our UI controls to InstructPix2Pix instructions
22
+ INSTRUCTION_MAPPING = {
23
+ "Eyes": {
24
+ "Larger": "make the eyes larger",
25
+ "Smaller": "make the eyes smaller",
26
+ "Change Color": "change the eye color to blue",
27
+ "Change Shape": "make the eyes more almond shaped"
28
+ },
29
+ "Nose": {
30
+ "Refine": "refine the nose shape",
31
+ "Reshape": "make the nose more straight",
32
+ "Resize": "make the nose smaller"
33
+ },
34
+ "Lips": {
35
+ "Fuller": "make the lips fuller",
36
+ "Thinner": "make the lips thinner",
37
+ "Change Color": "make the lips more red"
38
+ },
39
+ "Face Shape": {
40
+ "Slim": "make the face slimmer",
41
+ "Round": "make the face more round",
42
+ "Define Jawline": "define the jawline more",
43
+ "Soften Features": "soften the facial features"
44
+ },
45
+ "Hair": {
46
+ "Change Color": "change the hair color to blonde",
47
+ "Change Style": "make the hair wavy",
48
+ "Add Volume": "add more volume to the hair"
49
+ },
50
+ "Body": {
51
+ "Slim": "make the body slimmer",
52
+ "Athletic": "make the body more athletic",
53
+ "Curvy": "make the body more curvy",
54
+ "Muscular": "make the body more muscular"
55
+ }
56
+ }
57
+
58
+ # Function to process image using InstructPix2Pix
59
+ def process_with_instructpix2pix(image, feature_type, modification_type, intensity, custom_prompt="", use_custom_prompt=False):
60
+ if image is None:
61
+ return None, "Please upload an image first."
62
+
63
+ try:
64
+ # Prepare the instruction
65
+ if use_custom_prompt and custom_prompt:
66
+ instruction = custom_prompt
67
+ else:
68
+ instruction = INSTRUCTION_MAPPING[feature_type][modification_type]
69
+
70
+ # Adjust instruction based on intensity
71
+ if intensity < 0.3:
72
+ instruction = "slightly " + instruction
73
+ elif intensity > 0.7:
74
+ instruction = "dramatically " + instruction
75
+
76
+ # Convert image to base64 for API request
77
+ if isinstance(image, np.ndarray):
78
+ image_pil = Image.fromarray(image)
79
+ else:
80
+ image_pil = image
81
+
82
+ # Resize image if too large (InstructPix2Pix works best with images around 512x512)
83
+ width, height = image_pil.size
84
+ max_dim = 512
85
+ if width > max_dim or height > max_dim:
86
+ if width > height:
87
+ new_width = max_dim
88
+ new_height = int(height * (max_dim / width))
89
+ else:
90
+ new_height = max_dim
91
+ new_width = int(width * (max_dim / height))
92
+ image_pil = image_pil.resize((new_width, new_height), Image.LANCZOS)
93
+
94
+ # Convert to bytes for API request
95
+ buffered = io.BytesIO()
96
+ image_pil.save(buffered, format="PNG")
97
+ img_str = base64.b64encode(buffered.getvalue()).decode()
98
+
99
+ # Create API request to InstructPix2Pix Space
100
+ api_url = "https://timbrooks-instruct-pix2pix.hf.space/api/predict"
101
+ payload = {
102
+ "data": [
103
+ f"data:image/png;base64,{img_str}", # Input image
104
+ instruction, # Instruction
105
+ 50, # Steps
106
+ 7.5, # Text CFG
107
+ 1.5, # Image CFG
108
+ 1371, # Seed
109
+ False, # Randomize seed
110
+ True, # Fix CFG
111
+ False # Randomize CFG
112
+ ]
113
+ }
114
+
115
+ # Send request
116
+ response = requests.post(api_url, json=payload)
117
+
118
+ if response.status_code == 200:
119
+ result = response.json()
120
+
121
+ # Extract the output image
122
+ if 'data' in result and len(result['data']) >= 1:
123
+ output_data = result['data'][0]
124
+ if isinstance(output_data, str) and output_data.startswith('data:image'):
125
+ # Handle base64 encoded image
126
+ image_data = output_data.split(',')[1]
127
+ decoded_image = base64.b64decode(image_data)
128
+ output_image = Image.open(io.BytesIO(decoded_image))
129
+ return output_image, f"Edit completed successfully using instruction: '{instruction}'"
130
+
131
+ # If we couldn't parse the image from the response
132
+ return image, f"Error: Could not parse response from InstructPix2Pix."
133
+ else:
134
+ return image, f"Error: InstructPix2Pix returned status code {response.status_code}."
135
+
136
+ except Exception as e:
137
+ import traceback
138
+ traceback.print_exc()
139
+ return image, f"Error processing with InstructPix2Pix: {str(e)}"
140
+
141
+ # UI Components
142
+ def create_ui():
143
+ with gr.Blocks(title="AI-Powered Facial & Body Feature Editor") as app:
144
+ gr.Markdown("# AI-Powered Facial & Body Feature Editor")
145
+ gr.Markdown("Upload an image and use the controls to edit specific facial and body features using cloud GPU processing.")
146
+
147
+ with gr.Row():
148
+ with gr.Column(scale=1):
149
+ # Input controls
150
+ input_image = gr.Image(label="Upload Image", type="pil")
151
+
152
+ with gr.Group():
153
+ gr.Markdown("### Feature Selection")
154
+ feature_type = gr.Dropdown(
155
+ choices=FEATURE_TYPES,
156
+ label="Select Feature",
157
+ value="Eyes"
158
+ )
159
+
160
+ # Initialize with choices for the default feature (Eyes)
161
+ modification_type = gr.Dropdown(
162
+ choices=MODIFICATION_PRESETS["Eyes"],
163
+ label="Modification Type",
164
+ value="Larger"
165
+ )
166
+
167
+ intensity = gr.Slider(
168
+ minimum=0.1,
169
+ maximum=1.0,
170
+ value=0.5,
171
+ step=0.1,
172
+ label="Intensity"
173
+ )
174
+
175
+ with gr.Group():
176
+ gr.Markdown("### Custom Prompt (Advanced)")
177
+ use_custom_prompt = gr.Checkbox(
178
+ label="Use Custom Prompt",
179
+ value=False
180
+ )
181
+ custom_prompt = gr.Textbox(
182
+ label="Custom Prompt",
183
+ placeholder="e.g., make the eyes blue and add long eyelashes"
184
+ )
185
+
186
+ edit_button = gr.Button("Apply Edit", variant="primary")
187
+ reset_button = gr.Button("Reset")
188
+ status_text = gr.Textbox(label="Status", interactive=False)
189
+
190
+ with gr.Column(scale=1):
191
+ # Output display
192
+ output_image = gr.Image(label="Edited Image", type="pil")
193
+
194
+ with gr.Accordion("Edit History", open=False):
195
+ edit_history = gr.State([])
196
+ history_gallery = gr.Gallery(label="Previous Edits")
197
+
198
+ # Information about cloud processing
199
+ with gr.Accordion("Cloud GPU Processing Information", open=True):
200
+ gr.Markdown("""
201
+ ### About Cloud GPU Processing
202
+
203
+ This application uses InstructPix2Pix, a public GPU-accelerated Space on Hugging Face, to process your images.
204
+
205
+ **Benefits:**
206
+ - GPU-accelerated processing without local setup
207
+ - Works on any device with internet access
208
+ - No need to install CUDA or PyTorch
209
+
210
+ **How it works:**
211
+ 1. Your image is sent to the InstructPix2Pix Space
212
+ 2. Your feature selections are converted to text instructions
213
+ 3. The Space processes your image using GPU acceleration
214
+ 4. The edited image is returned to this interface
215
+
216
+ **Note:** Processing may take 10-30 seconds depending on server load.
217
+ """)
218
+
219
+ # Event handlers
220
+ def update_modification_choices(feature):
221
+ return gr.Dropdown(choices=MODIFICATION_PRESETS[feature])
222
+
223
+ feature_type.change(
224
+ fn=update_modification_choices,
225
+ inputs=feature_type,
226
+ outputs=modification_type
227
+ )
228
+
229
+ edit_button.click(
230
+ fn=process_with_instructpix2pix,
231
+ inputs=[
232
+ input_image,
233
+ feature_type,
234
+ modification_type,
235
+ intensity,
236
+ custom_prompt,
237
+ use_custom_prompt
238
+ ],
239
+ outputs=[output_image, status_text]
240
+ )
241
+
242
+ def reset_image():
243
+ return None, "Image reset."
244
+
245
+ reset_button.click(
246
+ fn=reset_image,
247
+ inputs=[],
248
+ outputs=[output_image, status_text]
249
+ )
250
+
251
+ # Add ethical usage notice
252
+ gr.Markdown("""
253
+ ## Ethical Usage Notice
254
+
255
+ This tool is designed for creative and personal use. Please ensure:
256
+
257
+ - You have appropriate rights to edit the images you upload
258
+ - You use this tool responsibly and respect the dignity of individuals
259
+ - You understand that AI-generated modifications are artificial and may not represent reality
260
+
261
+ By using this application, you agree to these terms.
262
+ """)
263
+
264
+ return app
265
+
266
+ # Launch the app
267
+ if __name__ == "__main__":
268
+ app = create_ui()
269
+ app.launch(server_name="0.0.0.0", share=False)