theoracle commited on
Commit
10bd531
·
1 Parent(s): 94d7430

Fix step error handling and wire up error_box

Browse files
Files changed (3) hide show
  1. app.py +121 -94
  2. app_remov.py +103 -0
  3. background_edit.py +4 -15
app.py CHANGED
@@ -1,103 +1,130 @@
1
  import os
2
-
3
- # ── Set cache/config dirs ──
4
- hf_home = "/data/.cache/huggingface"
5
- yolo_cfg = "/data/ultralytics"
6
- os.makedirs(hf_home, exist_ok=True)
7
- os.makedirs(yolo_cfg, exist_ok=True)
8
- os.environ["HF_HOME"] = hf_home
9
- os.environ["YOLO_CONFIG_DIR"] = yolo_cfg
10
-
11
- from ultralytics import YOLO
12
- import numpy as np
13
- import torch
14
  from PIL import Image
15
- import cv2
16
- from diffusers import StableDiffusionXLInpaintPipeline
17
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- # ---- utils ----
20
- def pil_to_cv2(pil_img):
21
- return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
22
-
23
- def cv2_to_pil(cv_img):
24
- return Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
25
-
26
- # ---- load models ----
27
- yolo = YOLO("yolov8x-seg.pt")
28
- inpaint_pipe = StableDiffusionXLInpaintPipeline.from_pretrained(
29
- "diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
30
- torch_dtype=torch.float16,
31
- use_safetensors=True,
32
- use_auth_token=os.getenv("HF_TOKEN")
33
- ).to("cuda")
34
-
35
- # ---- processing logic ----
36
- def run_background_removal_and_inpaint(image, prompt, negative_prompt):
37
- if image is None:
38
- raise gr.Error("Please upload an image.")
39
-
40
- img_cv = pil_to_cv2(image)
41
- results = yolo(img_cv)
42
-
43
- if not results or not results[0].masks or len(results[0].masks.data) == 0:
44
- raise gr.Error("No subject detected in the image. Please upload a clearer photo.")
45
-
46
- mask = results[0].masks.data[0].cpu().numpy()
47
- binary = (mask > 0.5).astype(np.uint8)
48
- background_mask = 1 - binary
49
- kernel = np.ones((15, 15), np.uint8)
50
- dilated = cv2.dilate(background_mask, kernel, iterations=1)
51
- inpaint_mask = (dilated * 255).astype(np.uint8)
52
-
53
- mask_pil = cv2_to_pil(inpaint_mask).resize((1024, 1024)).convert("L")
54
- img_pil = image.resize((1024, 1024)).convert("RGB")
55
-
56
- result = inpaint_pipe(
57
- prompt=prompt,
58
- negative_prompt=negative_prompt or "",
59
- image=img_pil,
60
- mask_image=mask_pil,
61
- guidance_scale=10,
62
- num_inference_steps=40
63
- ).images[0]
64
-
65
- return result
66
-
67
- # ---- Gradio interface ----
68
  with gr.Blocks() as demo:
69
- gr.Markdown("## 🖼️ Remove & Replace Background")
70
- gr.Markdown("Upload a headshot, and describe the desired new background.")
71
-
72
- with gr.Row():
73
- input_img = gr.Image(type="pil", label="Upload Image")
74
- output_img = gr.Image(type="pil", label="Result")
75
-
76
- with gr.Row():
77
- prompt = gr.Textbox(
78
- label="New Background Prompt",
79
- value="modern open-plan office, soft natural light, minimalistic decor"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  )
81
- neg_prompt = gr.Textbox(
82
- label="Negative Prompt",
83
- value="cartoon, fantasy, dark lighting, painting, anime"
84
- )
85
-
86
- error_box = gr.Markdown()
87
 
88
- def safe_run(img, prompt, neg_prompt):
89
- try:
90
- result = run_background_removal_and_inpaint(img, prompt, neg_prompt)
91
- return result, ""
92
- except Exception as e:
93
- print(f"[ERROR] {type(e).__name__}: {e}")
94
- return None, f"**❌ Error:** {type(e).__name__}: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
- run_btn = gr.Button("Run Background Inpaint")
97
- run_btn.click(
98
- fn=safe_run,
99
- inputs=[input_img, prompt, neg_prompt],
100
- outputs=[output_img, error_box]
101
- )
102
 
103
- demo.launch(debug=True)
 
1
  import os
2
+ import traceback
3
+ from datetime impor
4
+ import torch, gc
 
 
 
 
 
 
 
 
 
5
  from PIL import Image
 
 
6
  import gradio as gr
7
+ from inference import generate_with_lora
8
+ from background_edit import run_background_removal_and_inpaint
9
+
10
+
11
+ # ───────────────────── Helpers ─────────────────────
12
+ def _print_trace():
13
+ traceback.print_exc()
14
+
15
+ def safe_generate_with_lora(*a, **kw):
16
+ try:
17
+ return generate_with_lora(*a, **kw)
18
+ except gr.Error:
19
+ _print_trace()
20
+ raise
21
+ except Exception as e:
22
+ _print_trace()
23
+ raise gr.Error(f"Image generation failed: {e}")
24
+
25
+ def unload_models():
26
+ torch.cuda.empty_cache()
27
+ gc.collect()
28
+
29
+ def safe_run_background(image_path, *args, **kwargs):
30
+ try:
31
+ unload_models() # free VRAM before loading the inpainting model
32
+ return run_background_removal_and_inpaint(image_path, *args, **kwargs)
33
+ except Exception as e:
34
+ _print_trace()
35
+ raise gr.Error(f"[Step 2] Background replacement failed: {type(e).__name__}: {e}")
36
+
37
+ def _save_to_disk(img):
38
+ if img is None:
39
+ return gr.skip()
40
+
41
+ os.makedirs("/tmp/gradio_outputs", exist_ok=True)
42
+ ts = datetime.now().strftime("%Y%m%d_%H%M%S")
43
+ path = f"/tmp/gradio_outputs/step1_result_{ts}.png"
44
+ img.save(path)
45
+ return path
46
+
47
+
48
+ # ───────────────────── UI ─────────────────────
49
+ shared_output_path = gr.State() # holds file path to Step 1 output
50
+ original_input = gr.State() # holds the original upload (if needed)
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  with gr.Blocks() as demo:
53
+ demo.queue()
54
+
55
+ # ─────────── STEP 1: Headshot Refinement ───────────
56
+ with gr.Tab("Step 1: Headshot Refinement"):
57
+ with gr.Row():
58
+ input_image = gr.Image(type="pil", label="Upload Headshot")
59
+ output_image = gr.Image(type="pil", label="Refined Output")
60
+
61
+ with gr.Row():
62
+ prompt = gr.Textbox(
63
+ label="Prompt",
64
+ value="a professional corporate headshot of a confident woman in her 30s with blonde hair"
65
+ )
66
+ negative_prompt = gr.Textbox(
67
+ label="Negative Prompt",
68
+ value="deformed, cartoon, anime, illustration, painting, drawing, sketch, low resolution, blurry, out of focus, pixelated"
69
+ )
70
+
71
+ with gr.Row():
72
+ strength = gr.Slider(0.1, 1.0, value=0.20, step=0.05, label="Strength")
73
+ guidance = gr.Slider(1, 20, value=17.0, step=0.5, label="Guidance Scale")
74
+
75
+ run_btn = gr.Button("Generate")
76
+
77
+ event = (
78
+ run_btn.click(
79
+ fn=safe_generate_with_lora,
80
+ inputs=[input_image, prompt, negative_prompt, strength, guidance],
81
+ outputs=output_image,
82
+ )
83
+ .then(_save_to_disk, output_image, shared_output_path)
84
+ .then(lambda x: x, input_image, original_input)
85
  )
 
 
 
 
 
 
86
 
87
+ # ─────────── STEP 2: Background Replacement ───────────
88
+ with gr.Tab("Step 2: Replace Background"):
89
+ error_box = gr.Markdown(value="", visible=True)
90
+
91
+ with gr.Row():
92
+ inpaint_prompt = gr.Textbox(
93
+ label="New Background Prompt",
94
+ value="modern open-plan startup office background, natural lighting, glass walls, clean design, minimalistic decor"
95
+ )
96
+ inpaint_negative = gr.Textbox(
97
+ label="Negative Prompt",
98
+ value="dark lighting, cluttered background, fantasy elements, cartoon, anime, painting, low quality, distorted shapes"
99
+ )
100
+
101
+ with gr.Row():
102
+ inpaint_result = gr.Image(type="pil", label="Inpainted Image")
103
+
104
+ with gr.Row():
105
+ inpaint_btn = gr.Button("Remove Background & Inpaint", interactive=False)
106
+
107
+ def guarded_inpaint(image_path, prompt_bg, neg_bg):
108
+ if not image_path or not os.path.isfile(image_path):
109
+ return None, "**🛑 Error:** No valid headshot found — please run Step 1 first."
110
+
111
+ try:
112
+ print(f"[DEBUG] Loading image from: {image_path}", flush=True)
113
+ result = safe_run_background(image_path, prompt_bg, neg_bg)
114
+ return result, ""
115
+ except gr.Error as e:
116
+ print(f"[Step 2 gr.Error] {e}", flush=True)
117
+ return None, f"**🛑 Step 2 Failed:** {str(e)}"
118
+ except Exception as e:
119
+ print(f"[Step 2 UNEXPECTED ERROR] {type(e).__name__}: {e}", flush=True)
120
+ return None, f"**❌ Unexpected Error:** {type(e).__name__}: {e}"
121
+
122
+ inpaint_btn.click(
123
+ fn=guarded_inpaint,
124
+ inputs=[shared_output_path, inpaint_prompt, inpaint_negative],
125
+ outputs=[inpaint_result, error_box],
126
+ )
127
 
128
+ event.then(lambda: gr.update(interactive=True), None, inpaint_btn)
 
 
 
 
 
129
 
130
+ demo.launch(debug=True)
app_remov.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ # ── Set cache/config dirs ──
4
+ hf_home = "/data/.cache/huggingface"
5
+ yolo_cfg = "/data/ultralytics"
6
+ os.makedirs(hf_home, exist_ok=True)
7
+ os.makedirs(yolo_cfg, exist_ok=True)
8
+ os.environ["HF_HOME"] = hf_home
9
+ os.environ["YOLO_CONFIG_DIR"] = yolo_cfg
10
+
11
+ from ultralytics import YOLO
12
+ import numpy as np
13
+ import torch
14
+ from PIL import Image
15
+ import cv2
16
+ from diffusers import StableDiffusionXLInpaintPipeline
17
+ import gradio as gr
18
+
19
+ # ---- utils ----
20
+ def pil_to_cv2(pil_img):
21
+ return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
22
+
23
+ def cv2_to_pil(cv_img):
24
+ return Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
25
+
26
+ # ---- load models ----
27
+ yolo = YOLO("yolov8x-seg.pt")
28
+ inpaint_pipe = StableDiffusionXLInpaintPipeline.from_pretrained(
29
+ "diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
30
+ torch_dtype=torch.float16,
31
+ use_safetensors=True,
32
+ use_auth_token=os.getenv("HF_TOKEN")
33
+ ).to("cuda")
34
+
35
+ # ---- processing logic ----
36
+ def run_background_removal_and_inpaint(image, prompt, negative_prompt):
37
+ if image is None:
38
+ raise gr.Error("Please upload an image.")
39
+
40
+ img_cv = pil_to_cv2(image)
41
+ results = yolo(img_cv)
42
+
43
+ if not results or not results[0].masks or len(results[0].masks.data) == 0:
44
+ raise gr.Error("No subject detected in the image. Please upload a clearer photo.")
45
+
46
+ mask = results[0].masks.data[0].cpu().numpy()
47
+ binary = (mask > 0.5).astype(np.uint8)
48
+ background_mask = 1 - binary
49
+ kernel = np.ones((15, 15), np.uint8)
50
+ dilated = cv2.dilate(background_mask, kernel, iterations=1)
51
+ inpaint_mask = (dilated * 255).astype(np.uint8)
52
+
53
+ mask_pil = cv2_to_pil(inpaint_mask).resize((1024, 1024)).convert("L")
54
+ img_pil = image.resize((1024, 1024)).convert("RGB")
55
+
56
+ result = inpaint_pipe(
57
+ prompt=prompt,
58
+ negative_prompt=negative_prompt or "",
59
+ image=img_pil,
60
+ mask_image=mask_pil,
61
+ guidance_scale=10,
62
+ num_inference_steps=40
63
+ ).images[0]
64
+
65
+ return result
66
+
67
+ # ---- Gradio interface ----
68
+ with gr.Blocks() as demo:
69
+ gr.Markdown("## 🖼️ Remove & Replace Background")
70
+ gr.Markdown("Upload a headshot, and describe the desired new background.")
71
+
72
+ with gr.Row():
73
+ input_img = gr.Image(type="pil", label="Upload Image")
74
+ output_img = gr.Image(type="pil", label="Result")
75
+
76
+ with gr.Row():
77
+ prompt = gr.Textbox(
78
+ label="New Background Prompt",
79
+ value="modern open-plan office, soft natural light, minimalistic decor"
80
+ )
81
+ neg_prompt = gr.Textbox(
82
+ label="Negative Prompt",
83
+ value="cartoon, fantasy, dark lighting, painting, anime"
84
+ )
85
+
86
+ error_box = gr.Markdown()
87
+
88
+ def safe_run(img, prompt, neg_prompt):
89
+ try:
90
+ result = run_background_removal_and_inpaint(img, prompt, neg_prompt)
91
+ return result, ""
92
+ except Exception as e:
93
+ print(f"[ERROR] {type(e).__name__}: {e}")
94
+ return None, f"**❌ Error:** {type(e).__name__}: {e}"
95
+
96
+ run_btn = gr.Button("Run Background Inpaint")
97
+ run_btn.click(
98
+ fn=safe_run,
99
+ inputs=[input_img, prompt, neg_prompt],
100
+ outputs=[output_img, error_box]
101
+ )
102
+
103
+ demo.launch(debug=True)
background_edit.py CHANGED
@@ -33,38 +33,27 @@ inpaint_pipe = StableDiffusionXLInpaintPipeline.from_pretrained(
33
  use_auth_token=os.getenv("HF_TOKEN")
34
  ).to("cuda")
35
 
36
- def run_background_removal_and_inpaint(shared_output, prompt, negative_prompt):
37
-
38
- # Get image from shared_output
39
- if isinstance(shared_output, dict):
40
- image = shared_output.get("step1")
41
- else:
42
- image = None
43
-
44
- if image is None:
45
- raise gr.Error("Run Step 1 first to get a base image.")
46
 
 
47
  img_cv = pil_to_cv2(image)
48
  results = yolo(img_cv)
49
 
50
- # ✅ Validate YOLO detection result
51
  if not results or not results[0].masks or len(results[0].masks.data) == 0:
52
  raise gr.Error("No subject detected in the image. Please upload a clearer photo.")
53
 
54
  mask = results[0].masks.data[0].cpu().numpy()
55
-
56
- # Create inpainting mask
57
  binary = (mask > 0.5).astype(np.uint8)
58
  background_mask = 1 - binary
59
  kernel = np.ones((15, 15), np.uint8)
60
  dilated = cv2.dilate(background_mask, kernel, iterations=1)
61
  inpaint_mask = (dilated * 255).astype(np.uint8)
62
 
63
- # Resize and prepare images
64
  mask_pil = cv2_to_pil(inpaint_mask).resize((1024, 1024)).convert("L")
65
  img_pil = image.resize((1024, 1024)).convert("RGB")
66
 
67
- # Inpaint
68
  result = inpaint_pipe(
69
  prompt=prompt,
70
  negative_prompt=negative_prompt or "",
 
33
  use_auth_token=os.getenv("HF_TOKEN")
34
  ).to("cuda")
35
 
36
+ def run_background_removal_and_inpaint(image_path, prompt, negative_prompt):
37
+ if not image_path or not os.path.isfile(image_path):
38
+ raise gr.Error("No valid image found. Please run Step 1 first.")
 
 
 
 
 
 
 
39
 
40
+ image = Image.open(image_path).convert("RGB")
41
  img_cv = pil_to_cv2(image)
42
  results = yolo(img_cv)
43
 
 
44
  if not results or not results[0].masks or len(results[0].masks.data) == 0:
45
  raise gr.Error("No subject detected in the image. Please upload a clearer photo.")
46
 
47
  mask = results[0].masks.data[0].cpu().numpy()
 
 
48
  binary = (mask > 0.5).astype(np.uint8)
49
  background_mask = 1 - binary
50
  kernel = np.ones((15, 15), np.uint8)
51
  dilated = cv2.dilate(background_mask, kernel, iterations=1)
52
  inpaint_mask = (dilated * 255).astype(np.uint8)
53
 
 
54
  mask_pil = cv2_to_pil(inpaint_mask).resize((1024, 1024)).convert("L")
55
  img_pil = image.resize((1024, 1024)).convert("RGB")
56
 
 
57
  result = inpaint_pipe(
58
  prompt=prompt,
59
  negative_prompt=negative_prompt or "",