jhh6576 commited on
Commit
ca0ffa2
·
verified ·
1 Parent(s): 5b67c31

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -115
app.py CHANGED
@@ -1,145 +1,119 @@
1
- import os
2
  import gradio as gr
3
- import numpy as np
4
- import random
5
- import spaces
6
  import torch
 
 
7
  from diffusers import FluxImg2ImgPipeline
8
- from PIL import Image, ImageOps
9
- import io
10
- import base64
11
 
12
  # --- CONFIGURATION ---
13
- # We use bfloat16 for speed/memory on CPU
14
- dtype = torch.bfloat16
15
- device = "cpu"
16
-
17
- # We use Schnell because it is 10x faster on CPU than Dev or 'Klein'
18
- # If you explicitly have access to Flux 2, change this ID.
19
  MODEL_ID = "black-forest-labs/FLUX.1-schnell"
 
 
20
 
21
- print(f"Loading Model: {MODEL_ID} on {device}...")
22
 
23
- # Load Pipeline
24
  pipe = FluxImg2ImgPipeline.from_pretrained(
25
  MODEL_ID,
26
- torch_dtype=dtype
27
  )
28
 
29
- # CRITICAL CPU OPTIMIZATION
30
- # This replaces .to("cuda"). It loads the model in pieces so RAM doesn't crash.
31
- pipe.enable_model_cpu_offload()
32
-
33
- MAX_SEED = np.iinfo(np.int32).max
34
-
35
- # --- THE "INJECTION" LOGIC ---
36
- # This acts as a pre-processor injection. It forces the pixel data
37
- # to be symmetrical before the UNet even sees it.
38
- # This guarantees the "Face Lock".
39
- def inject_symmetry(image, side="Left"):
40
- if image is None:
41
- return None
42
-
43
- img = image.convert("RGB")
44
- w, h = img.size
45
- mid = w // 2
46
- arr = np.array(img)
47
 
48
- # Mathematical locking of geometry
 
 
 
 
 
49
  if side == "Left":
50
- target_half = arr[:, :mid, :] # Get Left
51
- mirrored = np.fliplr(target_half) # Mirror it
52
- locked_face = np.concatenate((target_half, mirrored), axis=1)
 
 
 
 
53
  else:
54
- target_half = arr[:, mid:, :] # Get Right
55
- mirrored = np.fliplr(target_half) # Mirror it
56
- locked_face = np.concatenate((mirrored, target_half), axis=1)
 
57
 
58
- return Image.fromarray(locked_face)
59
 
60
- # --- INFERENCE FUNCTION ---
61
- @spaces.GPU(duration=120) # Request GPU if available, falls back to CPU logic if not
62
- def infer(prompt, input_image, side_choice, strength, seed, randomize_seed, width, height, steps, guidance):
 
63
 
64
- if input_image is None:
65
- raise gr.Error("Please upload an image for face symmetry.")
66
-
67
- if randomize_seed:
68
- seed = random.randint(0, MAX_SEED)
69
-
70
- # 1. INJECT SYMMETRY
71
- # We process the image *before* the model touches it.
72
- print("Injecting symmetry constraints...")
73
- processed_image = inject_symmetry(input_image, side_choice)
74
 
75
- # Resize to be compatible with Flux (multiples of 16)
76
- w, h = processed_image.size
77
- w = (w // 16) * 16
78
- h = (h // 16) * 16
79
- processed_image = processed_image.resize((w, h))
80
-
81
- print("Running Flux to smooth seams...")
82
- generator = torch.Generator(device="cpu").manual_seed(seed)
83
 
84
- # 2. RUN DIFFUSION
85
- # We use the processed image as the base.
86
- # Strength is CRITICAL:
87
- # 0.1 - 0.30 = Locks Identity (Only fixes the seam)
88
- # 0.35+ = Starts changing the face
89
- result = pipe(
90
- prompt=prompt,
91
- image=processed_image,
92
- strength=strength,
93
- num_inference_steps=steps,
94
- guidance_scale=guidance,
95
- generator=generator
96
- ).images[0]
97
 
98
- return result, seed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- # --- UI SETUP ---
101
  css = """
102
- #col-container { max-width: 1000px; margin: 0 auto; }
103
  """
104
 
105
  with gr.Blocks(css=css) as demo:
106
  with gr.Column(elem_id="col-container"):
107
- gr.Markdown("# Flux Face Symmetry (Identity Lock)")
108
- gr.Markdown("CPU-Optimized Mode. Uses Pixel Injection to lock face geometry.")
109
 
110
  with gr.Row():
111
- with gr.Column():
112
- input_img = gr.Image(label="Upload Face", type="pil")
113
-
114
- with gr.Row():
115
- side = gr.Radio(["Left", "Right"], label="Keep Side", value="Left")
116
- # Strength default is 0.25 -> This ensures ID is locked
117
- strength = gr.Slider(0.1, 0.6, value=0.25, step=0.01, label="Denoise Strength (Keep <0.30 to lock ID)")
118
-
119
- prompt = gr.Text(
120
- label="Prompt (Optional - usually leave empty or describe lighting)",
121
- value="high quality, realistic, smooth skin, 8k",
122
- lines=2
123
- )
124
-
125
- run_btn = gr.Button("Generate Symmetrical Face", variant="primary")
126
-
127
- with gr.Accordion("Advanced", open=False):
128
- steps = gr.Slider(1, 50, value=4, step=1, label="Steps (Keep low for CPU)")
129
- guidance = gr.Slider(0, 10, value=1.0, step=0.1, label="Guidance")
130
- width = gr.Slider(256, 1024, value=1024, step=16, label="Width")
131
- height = gr.Slider(256, 1024, value=1024, step=16, label="Height")
132
- seed = gr.Slider(0, MAX_SEED, value=0, label="Seed")
133
- randomize_seed = gr.Checkbox(True, label="Randomize Seed")
134
-
135
- with gr.Column():
136
- output_img = gr.Image(label="Result")
137
- seed_output = gr.Number(label="Used Seed")
138
-
139
- run_btn.click(
140
- infer,
141
- inputs=[prompt, input_img, side, strength, seed, randomize_seed, width, height, steps, guidance],
142
- outputs=[output_img, seed_output]
143
- )
144
 
145
- demo.launch()
 
 
1
  import gradio as gr
 
 
 
2
  import torch
3
+ import numpy as np
4
+ import gc
5
  from diffusers import FluxImg2ImgPipeline
6
+ from PIL import Image
 
 
7
 
8
  # --- CONFIGURATION ---
9
+ # We use Flux.1-Schnell (The official fast/distilled model)
10
+ # It is the closest working alternative to your requested "4B" model.
 
 
 
 
11
  MODEL_ID = "black-forest-labs/FLUX.1-schnell"
12
+ DEVICE = "cpu"
13
+ DTYPE = torch.bfloat16
14
 
15
+ print(f"Loading {MODEL_ID} on {DEVICE}...")
16
 
17
+ # Load Model without GPU offloading (since we are on CPU)
18
  pipe = FluxImg2ImgPipeline.from_pretrained(
19
  MODEL_ID,
20
+ torch_dtype=DTYPE
21
  )
22
 
23
+ # --- THE INJECTION LOGIC ---
24
+ # You requested UNet injection. On CPU, the most efficient way to "Lock" the face
25
+ # is to mathematically force the symmetry on the input tensor (Latent/Pixel)
26
+ # BEFORE the UNet destroys the details.
27
+ def inject_face_symmetry(image, side="Left"):
28
+ if image is None: return None
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ # 1. Convert to Numpy Buffer
31
+ img_array = np.array(image.convert("RGB"))
32
+ height, width, _ = img_array.shape
33
+ midpoint = width // 2
34
+
35
+ # 2. Force Mathematical Symmetry (The "Lock")
36
  if side == "Left":
37
+ # Keep Left, Mirror to Right
38
+ left_side = img_array[:, :midpoint, :]
39
+ right_side = np.fliplr(left_side)
40
+ # Handle odd widths
41
+ if right_side.shape[1] != left_side.shape[1]:
42
+ right_side = right_side[:, :left_side.shape[1], :]
43
+ symmetrical_array = np.concatenate((left_side, right_side), axis=1)
44
  else:
45
+ # Keep Right, Mirror to Left
46
+ right_side = img_array[:, midpoint:, :]
47
+ left_side = np.fliplr(right_side)
48
+ symmetrical_array = np.concatenate((left_side, right_side), axis=1)
49
 
50
+ return Image.fromarray(symmetrical_array)
51
 
52
+ # --- INFERENCE ---
53
+ def run_inference(prompt, image_input, side, strength, seed, steps):
54
+ if image_input is None:
55
+ return None
56
 
57
+ # Clean RAM before starting
58
+ gc.collect()
 
 
 
 
 
 
 
 
59
 
60
+ # 1. INJECTION: Lock the Geometry
61
+ # We do this first so the UNet receives a perfectly symmetrical input.
62
+ print("Injecting Symmetry Code...")
63
+ locked_image = inject_face_symmetry(image_input, side)
 
 
 
 
64
 
65
+ # 2. RESIZE FOR CPU SAFETY
66
+ # Flux requires ~24GB RAM. Free CPUs usually have 16GB.
67
+ # We must resize to 512x512 or 768x768 max to avoid crashing.
68
+ w, h = locked_image.size
69
+ # Force resize to manageable CPU dimensions
70
+ locked_image = locked_image.resize((768, 768))
 
 
 
 
 
 
 
71
 
72
+ # 3. RUN FLUX (Refining the seams)
73
+ # We use a low strength (0.15 - 0.25) to preserve the identity (Lock)
74
+ # while letting the UNet fix the lighting/seams.
75
+ print("Running Flux UNet...")
76
+ generator = torch.Generator(DEVICE).manual_seed(int(seed))
77
+
78
+ try:
79
+ result = pipe(
80
+ prompt=prompt,
81
+ image=locked_image,
82
+ strength=strength,
83
+ num_inference_steps=steps,
84
+ guidance_scale=0.0, # Schnell uses 0 guidance usually
85
+ generator=generator
86
+ ).images[0]
87
+ except RuntimeError as e:
88
+ return None # Handle OOM gracefully if needed
89
+
90
+ return result
91
 
92
+ # --- UI ---
93
  css = """
94
+ #col-container { max-width: 900px; margin: 0 auto; }
95
  """
96
 
97
  with gr.Blocks(css=css) as demo:
98
  with gr.Column(elem_id="col-container"):
99
+ gr.Markdown(f"# Flux Face Symmetry (Identity Lock)")
100
+ gr.Markdown(f"Running **{MODEL_ID}** on CPU. <br/>Method: Pre-UNet Symmetry Injection.")
101
 
102
  with gr.Row():
103
+ img_in = gr.Image(label="Upload Face", type="pil")
104
+ img_out = gr.Image(label="Symmetrical Result")
105
+
106
+ with gr.Row():
107
+ side = gr.Radio(["Left", "Right"], label="Good Side", value="Left")
108
+ strength = gr.Slider(0.1, 0.45, value=0.20, step=0.01, label="Denoise (Lower = Stronger Lock)")
109
+
110
+ with gr.Accordion("Advanced Settings", open=False):
111
+ prompt = gr.Text(label="Prompt", value="high quality, realistic, smooth")
112
+ steps = gr.Slider(1, 10, value=2, step=1, label="Steps (Schnell only needs 2-4)")
113
+ seed = gr.Number(label="Seed", value=42)
114
+
115
+ btn = gr.Button("Generate", variant="primary")
116
+
117
+ btn.click(run_inference, inputs=[prompt, img_in, side, strength, seed, steps], outputs=[img_out])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
+ demo.queue().launch()