LogicGoInfotechSpaces commited on
Commit
cd11413
·
1 Parent(s): b0c5ec5

Add Gradio app (app.py), requirements for Space, and README

Browse files
Files changed (4) hide show
  1. README_gradio.md +18 -0
  2. app.py +165 -0
  3. requirements-gradio.txt +7 -0
  4. requirements.txt +7 -0
README_gradio.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Run locally
2
+
3
+ 1) Install core requirements from the repo (CUDA-compatible torch recommended).
4
+ 2) Install UI deps:
5
+
6
+ python3 -m pip install -r requirements-gradio.txt
7
+
8
+ 3) Ensure pretrained weights are available at `pretrained_models/` (or symlink).
9
+ 4) Launch:
10
+
11
+ python3 app.py
12
+
13
+ Hugging Face Space
14
+
15
+ - Add `app.py` as the entry point.
16
+ - Add `requirements.txt` and `requirements-gradio.txt` to the Space (merge if desired).
17
+ - Make sure all large weights are present in `pretrained_models/` (LFS) or fetched at build time.
18
+
app.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import logging
4
+ from typing import Tuple, Dict
5
+
6
+ import gradio as gr
7
+ from PIL import Image
8
+
9
+ from runners.simple_runner import SimpleRunner
10
+
11
+
12
+ # -----------------------------------------------------------------------------
13
+ # Logging (use lazy % formatting as requested)
14
+ # -----------------------------------------------------------------------------
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger("sfe-app")
17
+
18
+
19
+ # -----------------------------------------------------------------------------
20
+ # Model bootstrap (load once and reuse)
21
+ # -----------------------------------------------------------------------------
22
+ RUNNER: SimpleRunner | None = None
23
+
24
+
25
+ def get_runner() -> SimpleRunner:
26
+ global RUNNER
27
+ if RUNNER is None:
28
+ logger.info("Initializing SimpleRunner with %s", "pretrained_models/sfe_editor_light.pt")
29
+ RUNNER = SimpleRunner(
30
+ editor_ckpt_pth="pretrained_models/sfe_editor_light.pt",
31
+ )
32
+ return RUNNER
33
+
34
+
35
+ # -----------------------------------------------------------------------------
36
+ # Attribute catalog and recommended ranges
37
+ # -----------------------------------------------------------------------------
38
+ # Each entry maps a friendly attribute name to the internal editing name and a
39
+ # recommended power range for the slider.
40
+ ATTRIBUTE_MAP: Dict[str, Tuple[str, Tuple[float, float]]] = {
41
+ # Face semantics
42
+ "Smile": ("fs_smiling", (-10.0, 10.0)),
43
+ "Age": ("age", (-10.0, 10.0)), # interfacegan_directions
44
+ "Female features": ("gender", (-10.0, 7.0)), # stylespace_directions (positive adds femininity)
45
+
46
+ # Facial hair
47
+ # trimmed_beard removes beard for positive power; use negative to add
48
+ "Beard": ("trimmed_beard", (-30.0, 30.0)),
49
+ # goatee removes goatee for positive; negative tends to add
50
+ "Mustache/Goatee": ("goatee", (-7.0, 7.0)),
51
+
52
+ # Accessories & cosmetics
53
+ "Glasses": ("fs_glasses", (-20.0, 30.0)),
54
+ "Makeup": ("fs_makeup", (-10.0, 15.0)),
55
+
56
+ # Hair style (pretrained mappers)
57
+ "Curly hair": ("curly_hair", (0.0, 0.12)), # styleclip_directions
58
+ "Afro": ("afro", (0.0, 0.14)),
59
+
60
+ # Hair color via global text mapper
61
+ # You can also type custom prompts below
62
+ "Orange hair (text)": ("styleclip_global_a face_a face with orange hair_0.18", (0.0, 0.2)),
63
+ "Blonde hair (text)": ("styleclip_global_a face_a face with blonde hair_0.18", (0.0, 0.2)),
64
+ }
65
+
66
+
67
+ def recommended_range(attr_name: str) -> Tuple[float, float]:
68
+ edit_name, rng = ATTRIBUTE_MAP[attr_name]
69
+ return rng
70
+
71
+
72
+ def run_edit(
73
+ image: Image.Image,
74
+ attribute: str,
75
+ strength: float,
76
+ align_face: bool,
77
+ use_bg_mask: bool,
78
+ custom_text_edit: str,
79
+ ) -> Image.Image:
80
+ """Run a single attribute edit and return the edited image."""
81
+ runner = get_runner()
82
+
83
+ # Determine editing name and clip strength into the suggested range
84
+ edit_name, (lo, hi) = ATTRIBUTE_MAP[attribute]
85
+ if custom_text_edit and attribute.endswith("(text)"):
86
+ # Allow overriding the default text prompt
87
+ if custom_text_edit.strip():
88
+ edit_name = custom_text_edit.strip()
89
+
90
+ clipped_strength = max(lo, min(hi, strength))
91
+ if clipped_strength != strength:
92
+ logger.info("Clipped strength from %s to %s for %s", strength, clipped_strength, attribute)
93
+
94
+ # Persist input to a temp file for the runner
95
+ with tempfile.TemporaryDirectory() as tmpdir:
96
+ inp_path = os.path.join(tmpdir, "input.jpg")
97
+ out_path = os.path.join(tmpdir, "edited.jpg")
98
+ image.convert("RGB").save(inp_path)
99
+
100
+ logger.info("Editing %s with power %s", edit_name, clipped_strength)
101
+ _ = runner.edit(
102
+ orig_img_pth=inp_path,
103
+ editing_name=edit_name,
104
+ edited_power=clipped_strength,
105
+ save_pth=out_path,
106
+ align=align_face,
107
+ use_mask=use_bg_mask,
108
+ )
109
+
110
+ return Image.open(out_path).convert("RGB")
111
+
112
+
113
+ def build_ui() -> gr.Blocks:
114
+ with gr.Blocks(css="footer {visibility: hidden}") as demo:
115
+ gr.Markdown("""
116
+ **StyleFeatureEditor – Facial Attribute Editing**
117
+ Upload a face and apply edits like smile, age, beard, hair style/color, glasses, and makeup.
118
+ Tip: For Beard/Goatee, negative strength tends to add facial hair.
119
+ """)
120
+
121
+ with gr.Row():
122
+ with gr.Column():
123
+ inp = gr.Image(type="pil", label="Input face", sources=["upload", "clipboard"])
124
+ attr = gr.Dropdown(
125
+ choices=list(ATTRIBUTE_MAP.keys()),
126
+ value="Smile",
127
+ label="Attribute",
128
+ )
129
+ strength = gr.Slider(-15, 15, value=5, step=0.01, label="Strength (p)")
130
+ align_face = gr.Checkbox(value=False, label="Align face before editing")
131
+ use_bg_mask = gr.Checkbox(value=False, label="Use background mask (reduce artifacts)")
132
+ custom_text = gr.Textbox(
133
+ value="",
134
+ label="Custom text edit (StyleCLIP Global Mapper)",
135
+ placeholder="styleclip_global_a face_a face with black hair_0.18",
136
+ )
137
+ run_btn = gr.Button("Run edit")
138
+
139
+ with gr.Column():
140
+ out = gr.Image(type="pil", label="Edited output")
141
+
142
+ # Update slider range based on attribute selection
143
+ def _on_attr_change(name: str):
144
+ lo, hi = recommended_range(name)
145
+ # Keep current value within new bounds
146
+ new_val = max(lo, min(hi, strength.value if hasattr(strength, "value") else 0))
147
+ return gr.Slider.update(minimum=lo, maximum=hi, value=new_val)
148
+
149
+ attr.change(_on_attr_change, inputs=attr, outputs=strength)
150
+
151
+ run_btn.click(
152
+ fn=run_edit,
153
+ inputs=[inp, attr, strength, align_face, use_bg_mask, custom_text],
154
+ outputs=out,
155
+ )
156
+
157
+ return demo
158
+
159
+
160
+ if __name__ == "__main__":
161
+ app = build_ui()
162
+ # On Spaces, the port/host are managed by the platform; run local defaults otherwise
163
+ app.launch()
164
+
165
+
requirements-gradio.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio==4.44.0
2
+ numpy>=1.23
3
+ Pillow>=9.5
4
+ torch
5
+ torchvision
6
+ omegaconf==2.1.2
7
+
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio==4.44.0
2
+ numpy>=1.23
3
+ Pillow>=9.5
4
+ torch
5
+ torchvision
6
+ omegaconf==2.1.2
7
+