Rishi Desai commited on
Commit
a125be2
·
1 Parent(s): bf54c2a

clean up, readme

Browse files
FaceEnhancementProd.py CHANGED
@@ -7,7 +7,9 @@ import torch
7
  BASE_PATH = "./"
8
  COMFYUI_PATH = os.path.join(BASE_PATH, "ComfyUI")
9
 
10
- # Declare models as a global variable at the top of the script
 
 
11
  models = None
12
 
13
  def get_value_at_index(obj: Union[Sequence, Mapping], index: int) -> Any:
 
7
  BASE_PATH = "./"
8
  COMFYUI_PATH = os.path.join(BASE_PATH, "ComfyUI")
9
 
10
+ """
11
+ To avoid loading the models each time, we store them in a global variable.
12
+ """
13
  models = None
14
 
15
  def get_value_at_index(obj: Union[Sequence, Mapping], index: int) -> Any:
README.md CHANGED
@@ -40,14 +40,20 @@ This will
40
  - Install ComfyUI, custom nodes, and required dependencies to your venv
41
  - Download all required models (Flux.1-dev, ControlNet, text encoders, PuLID, and more)
42
 
 
 
 
 
 
 
43
  ## Running on ComfyUI
44
 
45
  Using the ComfyUI workflows is the fastest way to get started. Run `python run_comfy.py`
46
  - `./workflows/FaceEnhancementProd.json` for face enhancement
47
- - `./workflows/FaceEmbedDist.json` for computing the face embed distance
48
 
49
 
50
- ## Configuration
51
 
52
  Create a .env file in the project root directory with your API keys:
53
  ```
@@ -55,9 +61,9 @@ touch .env
55
  echo "FAL_API_KEY=your_fal_api_key_here" >> .env
56
  ```
57
 
58
- The FAL API key is used for face upscaling during preprocessing. You can get one at [fal.ai](https://fal.ai/).
59
 
60
- # Gradio Demo
61
 
62
  A simple web interface for the face enhancement workflow.
63
 
@@ -70,6 +76,7 @@ python gradio_demo.py
70
 
71
  ### Notes
72
  - The script and demo run a ComfyUI server ephemerally
 
73
  - All images are saved in ./ComfyUI/input/scratch/
74
  - Temporary files are created during processing and cleaned up afterward
75
 
 
40
  - Install ComfyUI, custom nodes, and required dependencies to your venv
41
  - Download all required models (Flux.1-dev, ControlNet, text encoders, PuLID, and more)
42
 
43
+ 4. Run inference on one example:
44
+
45
+ ```
46
+ python main.py --input examples/dany_gpt_1.png --ref examples/dany_face.jpg --out examples/dany_enhanced.png
47
+ ```
48
+
49
  ## Running on ComfyUI
50
 
51
  Using the ComfyUI workflows is the fastest way to get started. Run `python run_comfy.py`
52
  - `./workflows/FaceEnhancementProd.json` for face enhancement
53
+ - `./workflows/FaceEmbedDist.json` for computing the face embedding distance
54
 
55
 
56
+ <!-- ## Configuration
57
 
58
  Create a .env file in the project root directory with your API keys:
59
  ```
 
61
  echo "FAL_API_KEY=your_fal_api_key_here" >> .env
62
  ```
63
 
64
+ The FAL API key is used for face upscaling during preprocessing. You can get one at [fal.ai](https://fal.ai/). -->
65
 
66
+ ## Gradio Demo
67
 
68
  A simple web interface for the face enhancement workflow.
69
 
 
76
 
77
  ### Notes
78
  - The script and demo run a ComfyUI server ephemerally
79
+ - Gradio demo faster than the script since models remain loaded in memory
80
  - All images are saved in ./ComfyUI/input/scratch/
81
  - Temporary files are created during processing and cleaned up afterward
82
 
chatgpt_woman_2.png DELETED

Git LFS Details

  • SHA256: a21c3b774300b86c58c733daa39fdd522bfa916826950bc16e616d6abdd3b9b2
  • Pointer size: 132 Bytes
  • Size of remote file: 1.65 MB
demo.py CHANGED
@@ -1,10 +1,33 @@
1
  import gradio as gr
2
  import os
3
  import tempfile
 
 
 
 
 
4
  from main import process_face
5
  from PIL import Image
6
 
7
  PORT = 7860
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  def enhance_face_gradio(input_image, ref_image):
10
  """
@@ -17,6 +40,23 @@ def enhance_face_gradio(input_image, ref_image):
17
  Returns:
18
  PIL Image: Enhanced image
19
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  # Create temporary files for input, reference, and output
21
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as input_file, \
22
  tempfile.NamedTemporaryFile(suffix=".png", delete=False) as ref_file, \
@@ -39,18 +79,27 @@ def enhance_face_gradio(input_image, ref_image):
39
  upscale=False,
40
  output_path=output_path
41
  )
42
- pass
43
  except Exception as e:
44
  # Handle the error, log it, and return an error message
45
  print(f"Error processing face: {e}")
46
  return "An error occurred while processing the face. Please try again."
47
-
48
  finally:
49
  # Clean up temporary input and reference files
50
  os.unlink(input_path)
51
  os.unlink(ref_path)
52
 
53
- return Image.open(output_path)
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  def create_gradio_interface():
56
  # Create the Gradio interface
 
1
  import gradio as gr
2
  import os
3
  import tempfile
4
+ import hashlib
5
+ import io
6
+ import pickle
7
+ import pathlib
8
+ import sys
9
  from main import process_face
10
  from PIL import Image
11
 
12
  PORT = 7860
13
+ CACHE_DIR = "./cache"
14
+
15
+ # Ensure cache directory exists
16
+ os.makedirs(CACHE_DIR, exist_ok=True)
17
+
18
+ def get_image_hash(img):
19
+ """
20
+ Generate a hash of the image content.
21
+
22
+ Args:
23
+ img: PIL Image
24
+
25
+ Returns:
26
+ str: Hash of the image
27
+ """
28
+ img_bytes = io.BytesIO()
29
+ img.save(img_bytes, format='PNG')
30
+ return hashlib.md5(img_bytes.getvalue()).hexdigest()
31
 
32
  def enhance_face_gradio(input_image, ref_image):
33
  """
 
40
  Returns:
41
  PIL Image: Enhanced image
42
  """
43
+ # Generate hashes for both images
44
+ input_hash = get_image_hash(input_image)
45
+ ref_hash = get_image_hash(ref_image)
46
+ combined_hash = f"{input_hash}_{ref_hash}"
47
+ cache_path = os.path.join(CACHE_DIR, f"{combined_hash}.pkl")
48
+
49
+ # Check if result exists in cache
50
+ if os.path.exists(cache_path):
51
+ try:
52
+ with open(cache_path, 'rb') as f:
53
+ result_img = pickle.load(f)
54
+ print(f"Returning cached result for images with hash {combined_hash}")
55
+ return result_img
56
+ except (pickle.PickleError, IOError) as e:
57
+ print(f"Error loading from cache: {e}")
58
+ # Continue to processing if cache load fails
59
+
60
  # Create temporary files for input, reference, and output
61
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as input_file, \
62
  tempfile.NamedTemporaryFile(suffix=".png", delete=False) as ref_file, \
 
79
  upscale=False,
80
  output_path=output_path
81
  )
 
82
  except Exception as e:
83
  # Handle the error, log it, and return an error message
84
  print(f"Error processing face: {e}")
85
  return "An error occurred while processing the face. Please try again."
 
86
  finally:
87
  # Clean up temporary input and reference files
88
  os.unlink(input_path)
89
  os.unlink(ref_path)
90
 
91
+ # Load the output image
92
+ result_img = Image.open(output_path)
93
+
94
+ # Cache the result
95
+ try:
96
+ with open(cache_path, 'wb') as f:
97
+ pickle.dump(result_img, f)
98
+ print(f"Cached result for images with hash {combined_hash}")
99
+ except (pickle.PickleError, IOError) as e:
100
+ print(f"Error caching result: {e}")
101
+
102
+ return result_img
103
 
104
  def create_gradio_interface():
105
  # Create the Gradio interface
main.py CHANGED
@@ -10,6 +10,7 @@ def parse_args():
10
  parser.add_argument('--crop', action='store_true', help='Whether to crop the image')
11
  parser.add_argument('--upscale', action='store_true', help='Whether to upscale the image')
12
  parser.add_argument('--output', type=str, required=True, help='Path to save the output image')
 
13
  args = parser.parse_args()
14
 
15
  # Validate input file exists
@@ -46,7 +47,7 @@ def create_scratch_dir():
46
 
47
  return new_dir
48
 
49
- def process_face(input_path, ref_path, crop=False, upscale=False, output_path=None):
50
  """
51
  Process a face image using the given parameters.
52
 
@@ -83,7 +84,7 @@ def process_face(input_path, ref_path, crop=False, upscale=False, output_path=No
83
  comfy_ref_path = os.path.relpath(scratch_ref, "./ComfyUI/input")
84
  comfy_input_path = os.path.relpath(scratch_input, "./ComfyUI/input")
85
 
86
- enhance_face(comfy_ref_path, comfy_input_path, output_path, dist_image=f"{output_path}_dist.png", id_weight=0.75)
87
 
88
  print(f"Enhanced image saved to: {output_path}")
89
  print(f"Working files are in: {scratch_dir}")
@@ -97,7 +98,8 @@ def main():
97
  ref_path=args.ref,
98
  crop=args.crop,
99
  upscale=args.upscale,
100
- output_path=args.output
 
101
  )
102
 
103
  if __name__ == "__main__":
 
10
  parser.add_argument('--crop', action='store_true', help='Whether to crop the image')
11
  parser.add_argument('--upscale', action='store_true', help='Whether to upscale the image')
12
  parser.add_argument('--output', type=str, required=True, help='Path to save the output image')
13
+ parser.add_argument('--id_weight', type=float, default=0.75, help='face ID weight')
14
  args = parser.parse_args()
15
 
16
  # Validate input file exists
 
47
 
48
  return new_dir
49
 
50
+ def process_face(input_path, ref_path, crop=False, upscale=False, output_path=None, id_weight=0.75):
51
  """
52
  Process a face image using the given parameters.
53
 
 
84
  comfy_ref_path = os.path.relpath(scratch_ref, "./ComfyUI/input")
85
  comfy_input_path = os.path.relpath(scratch_input, "./ComfyUI/input")
86
 
87
+ enhance_face(comfy_ref_path, comfy_input_path, output_path, dist_image=f"{output_path}_dist.png", id_weight=id_weight)
88
 
89
  print(f"Enhanced image saved to: {output_path}")
90
  print(f"Working files are in: {scratch_dir}")
 
98
  ref_path=args.ref,
99
  crop=args.crop,
100
  upscale=args.upscale,
101
+ output_path=args.output,
102
+ id_weight=args.id_weight
103
  )
104
 
105
  if __name__ == "__main__":
out.png DELETED

Git LFS Details

  • SHA256: a3d70afa36535542edeb1a7c3423e06a025e13309a5fe88b73c037ee8b5c438f
  • Pointer size: 132 Bytes
  • Size of remote file: 1.38 MB
out.png_dist.png DELETED

Git LFS Details

  • SHA256: 1f867a955810a3307a394ddf85b499a1c1a0e525b32436a22b7660a3412e1d2e
  • Pointer size: 132 Bytes
  • Size of remote file: 1.38 MB
requirements.txt CHANGED
@@ -3,7 +3,6 @@ huggingface_hub[hf_transfer]
3
  comfy-cli
4
  python-dotenv
5
  requests
6
- openai
7
  fal-client
8
  gradio>=3.50.2
9
  pillow>=10.0.0
 
3
  comfy-cli
4
  python-dotenv
5
  requests
 
6
  fal-client
7
  gradio>=3.50.2
8
  pillow>=10.0.0
scratch/timothee_face.jpg DELETED

Git LFS Details

  • SHA256: 02d536be829499f0ca82fe04cbf2c5fd4384a97a5f8bfc242bc8cd2d6bfc58e4
  • Pointer size: 130 Bytes
  • Size of remote file: 50 kB
woman_face.jpg DELETED

Git LFS Details

  • SHA256: 7efc9e834ada7fb6f61cfad6d546c56071c50fc8f60cb3e6ead9d2ac13c3a560
  • Pointer size: 132 Bytes
  • Size of remote file: 1.87 MB
workflows/FaceDistanceProd.json ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "last_node_id": 8,
3
+ "last_link_id": 8,
4
+ "nodes": [
5
+ {
6
+ "id": 1,
7
+ "type": "FaceEmbedDistance",
8
+ "pos": [
9
+ 4701.9052734375,
10
+ 1165.7012939453125
11
+ ],
12
+ "size": [
13
+ 315,
14
+ 170
15
+ ],
16
+ "flags": {},
17
+ "order": 4,
18
+ "mode": 0,
19
+ "inputs": [
20
+ {
21
+ "name": "analysis_models",
22
+ "type": "ANALYSIS_MODELS",
23
+ "link": 1
24
+ },
25
+ {
26
+ "name": "reference",
27
+ "type": "IMAGE",
28
+ "link": 5
29
+ },
30
+ {
31
+ "name": "image",
32
+ "type": "IMAGE",
33
+ "link": 7
34
+ }
35
+ ],
36
+ "outputs": [
37
+ {
38
+ "name": "IMAGE",
39
+ "type": "IMAGE",
40
+ "links": [
41
+ 2
42
+ ],
43
+ "slot_index": 0
44
+ },
45
+ {
46
+ "name": "distance",
47
+ "type": "FLOAT",
48
+ "links": null
49
+ }
50
+ ],
51
+ "properties": {
52
+ "Node name for S&R": "FaceEmbedDistance"
53
+ },
54
+ "widgets_values": [
55
+ "cosine",
56
+ 100,
57
+ 0,
58
+ true
59
+ ]
60
+ },
61
+ {
62
+ "id": 2,
63
+ "type": "PreviewImage",
64
+ "pos": [
65
+ 5130.59228515625,
66
+ 1011.3296508789062
67
+ ],
68
+ "size": [
69
+ 375.2000732421875,
70
+ 279.4446716308594
71
+ ],
72
+ "flags": {},
73
+ "order": 6,
74
+ "mode": 0,
75
+ "inputs": [
76
+ {
77
+ "name": "images",
78
+ "type": "IMAGE",
79
+ "link": 2
80
+ }
81
+ ],
82
+ "outputs": [],
83
+ "properties": {
84
+ "Node name for S&R": "PreviewImage"
85
+ },
86
+ "widgets_values": []
87
+ },
88
+ {
89
+ "id": 3,
90
+ "type": "FaceEmbedDistance",
91
+ "pos": [
92
+ 4699.32275390625,
93
+ 1407.7418212890625
94
+ ],
95
+ "size": [
96
+ 315,
97
+ 170
98
+ ],
99
+ "flags": {},
100
+ "order": 5,
101
+ "mode": 0,
102
+ "inputs": [
103
+ {
104
+ "name": "analysis_models",
105
+ "type": "ANALYSIS_MODELS",
106
+ "link": 3
107
+ },
108
+ {
109
+ "name": "reference",
110
+ "type": "IMAGE",
111
+ "link": 6
112
+ },
113
+ {
114
+ "name": "image",
115
+ "type": "IMAGE",
116
+ "link": 8
117
+ }
118
+ ],
119
+ "outputs": [
120
+ {
121
+ "name": "IMAGE",
122
+ "type": "IMAGE",
123
+ "links": [
124
+ 4
125
+ ],
126
+ "slot_index": 0
127
+ },
128
+ {
129
+ "name": "distance",
130
+ "type": "FLOAT",
131
+ "links": null
132
+ }
133
+ ],
134
+ "properties": {
135
+ "Node name for S&R": "FaceEmbedDistance"
136
+ },
137
+ "widgets_values": [
138
+ "cosine",
139
+ 100,
140
+ 0,
141
+ true
142
+ ]
143
+ },
144
+ {
145
+ "id": 5,
146
+ "type": "PreviewImage",
147
+ "pos": [
148
+ 5118.4765625,
149
+ 1408.8367919921875
150
+ ],
151
+ "size": [
152
+ 375.2000732421875,
153
+ 279.4446716308594
154
+ ],
155
+ "flags": {},
156
+ "order": 7,
157
+ "mode": 0,
158
+ "inputs": [
159
+ {
160
+ "name": "images",
161
+ "type": "IMAGE",
162
+ "link": 4
163
+ }
164
+ ],
165
+ "outputs": [],
166
+ "properties": {
167
+ "Node name for S&R": "PreviewImage"
168
+ },
169
+ "widgets_values": []
170
+ },
171
+ {
172
+ "id": 4,
173
+ "type": "FaceAnalysisModels",
174
+ "pos": [
175
+ 4041.2080078125,
176
+ 1888.7574462890625
177
+ ],
178
+ "size": [
179
+ 315,
180
+ 82
181
+ ],
182
+ "flags": {},
183
+ "order": 0,
184
+ "mode": 0,
185
+ "inputs": [],
186
+ "outputs": [
187
+ {
188
+ "name": "ANALYSIS_MODELS",
189
+ "type": "ANALYSIS_MODELS",
190
+ "links": [
191
+ 1,
192
+ 3
193
+ ],
194
+ "slot_index": 0
195
+ }
196
+ ],
197
+ "properties": {
198
+ "Node name for S&R": "FaceAnalysisModels"
199
+ },
200
+ "widgets_values": [
201
+ "insightface",
202
+ "CUDA"
203
+ ]
204
+ },
205
+ {
206
+ "id": 6,
207
+ "type": "LoadImage",
208
+ "pos": [
209
+ 4040.829345703125,
210
+ 790.5192260742188
211
+ ],
212
+ "size": [
213
+ 315,
214
+ 314
215
+ ],
216
+ "flags": {},
217
+ "order": 1,
218
+ "mode": 0,
219
+ "inputs": [],
220
+ "outputs": [
221
+ {
222
+ "name": "IMAGE",
223
+ "type": "IMAGE",
224
+ "links": [
225
+ 5,
226
+ 6
227
+ ],
228
+ "slot_index": 0
229
+ },
230
+ {
231
+ "name": "MASK",
232
+ "type": "MASK",
233
+ "links": null
234
+ }
235
+ ],
236
+ "properties": {
237
+ "Node name for S&R": "LoadImage"
238
+ },
239
+ "widgets_values": [
240
+ "dany_face.png",
241
+ "image"
242
+ ]
243
+ },
244
+ {
245
+ "id": 7,
246
+ "type": "LoadImage",
247
+ "pos": [
248
+ 4036.8935546875,
249
+ 1155.6903076171875
250
+ ],
251
+ "size": [
252
+ 315,
253
+ 314
254
+ ],
255
+ "flags": {},
256
+ "order": 2,
257
+ "mode": 0,
258
+ "inputs": [],
259
+ "outputs": [
260
+ {
261
+ "name": "IMAGE",
262
+ "type": "IMAGE",
263
+ "links": [
264
+ 7
265
+ ],
266
+ "slot_index": 0
267
+ },
268
+ {
269
+ "name": "MASK",
270
+ "type": "MASK",
271
+ "links": null
272
+ }
273
+ ],
274
+ "properties": {
275
+ "Node name for S&R": "LoadImage"
276
+ },
277
+ "widgets_values": [
278
+ "chatgpt_dany_1.png",
279
+ "image"
280
+ ]
281
+ },
282
+ {
283
+ "id": 8,
284
+ "type": "LoadImage",
285
+ "pos": [
286
+ 4037.78955078125,
287
+ 1522.9161376953125
288
+ ],
289
+ "size": [
290
+ 315,
291
+ 314
292
+ ],
293
+ "flags": {},
294
+ "order": 3,
295
+ "mode": 0,
296
+ "inputs": [],
297
+ "outputs": [
298
+ {
299
+ "name": "IMAGE",
300
+ "type": "IMAGE",
301
+ "links": [
302
+ 8
303
+ ],
304
+ "slot_index": 0
305
+ },
306
+ {
307
+ "name": "MASK",
308
+ "type": "MASK",
309
+ "links": null
310
+ }
311
+ ],
312
+ "properties": {
313
+ "Node name for S&R": "LoadImage"
314
+ },
315
+ "widgets_values": [
316
+ "chatgpt_dany_2.png",
317
+ "image"
318
+ ]
319
+ }
320
+ ],
321
+ "links": [
322
+ [
323
+ 1,
324
+ 4,
325
+ 0,
326
+ 1,
327
+ 0,
328
+ "ANALYSIS_MODELS"
329
+ ],
330
+ [
331
+ 2,
332
+ 1,
333
+ 0,
334
+ 2,
335
+ 0,
336
+ "IMAGE"
337
+ ],
338
+ [
339
+ 3,
340
+ 4,
341
+ 0,
342
+ 3,
343
+ 0,
344
+ "ANALYSIS_MODELS"
345
+ ],
346
+ [
347
+ 4,
348
+ 3,
349
+ 0,
350
+ 5,
351
+ 0,
352
+ "IMAGE"
353
+ ],
354
+ [
355
+ 5,
356
+ 6,
357
+ 0,
358
+ 1,
359
+ 1,
360
+ "IMAGE"
361
+ ],
362
+ [
363
+ 6,
364
+ 6,
365
+ 0,
366
+ 3,
367
+ 1,
368
+ "IMAGE"
369
+ ],
370
+ [
371
+ 7,
372
+ 7,
373
+ 0,
374
+ 1,
375
+ 2,
376
+ "IMAGE"
377
+ ],
378
+ [
379
+ 8,
380
+ 8,
381
+ 0,
382
+ 3,
383
+ 2,
384
+ "IMAGE"
385
+ ]
386
+ ],
387
+ "groups": [],
388
+ "config": {},
389
+ "extra": {
390
+ "ds": {
391
+ "scale": 0.8390545288824041,
392
+ "offset": [
393
+ -4016.1357592321388,
394
+ -971.9468925845375
395
+ ]
396
+ },
397
+ "node_versions": {
398
+ "comfyui_faceanalysis": "4919e4e931db0edb219ba5086b3c10b8af750631",
399
+ "comfy-core": "0.3.26"
400
+ },
401
+ "ue_links": []
402
+ },
403
+ "version": 0.4
404
+ }