Update app.py
Browse files
app.py
CHANGED
@@ -12,240 +12,206 @@ import numpy as np
|
|
12 |
import cv2
|
13 |
from skimage import exposure
|
14 |
import torch
|
|
|
15 |
|
16 |
logging.basicConfig(level=logging.DEBUG)
|
17 |
logger = logging.getLogger(__name__)
|
18 |
load_dotenv()
|
19 |
|
20 |
-
|
21 |
-
|
22 |
-
"
|
23 |
-
"
|
24 |
-
"
|
25 |
-
"
|
26 |
-
"Cubisme": {"prompt": "cubist style, geometric forms, multiple perspectives", "params": {"geometric_strength": 0.8}}
|
27 |
},
|
28 |
-
"
|
29 |
-
"
|
30 |
-
"
|
31 |
-
"
|
32 |
},
|
33 |
-
"
|
34 |
-
"
|
35 |
-
"
|
36 |
-
"
|
37 |
-
"Vintage": {"prompt": "vintage photography, aged effect, retro colors", "params": {"grain": 0.4}}
|
38 |
},
|
39 |
-
"
|
40 |
-
"
|
41 |
-
"
|
42 |
-
"
|
43 |
-
}
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
"
|
50 |
-
"Dark_Fantasy": {"prompt": "dark fantasy, gothic elements, mysterious atmosphere", "params": {"darkness": 0.7}},
|
51 |
-
"Steampunk": {"prompt": "steampunk style, brass and copper, mechanical elements", "params": {"mechanical": 0.9}}
|
52 |
-
},
|
53 |
-
"Abstrait": {
|
54 |
-
"Holographique": {"prompt": "holographic effect, iridescent colors, light refraction", "params": {"iridescence": 0.8}},
|
55 |
-
"Fractal": {"prompt": "fractal art, recursive patterns, mathematical beauty", "params": {"complexity": 0.9}}
|
56 |
},
|
57 |
-
"
|
58 |
-
"
|
59 |
-
"
|
60 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
},
|
62 |
-
"
|
63 |
-
"
|
64 |
-
"
|
|
|
65 |
}
|
66 |
}
|
|
|
67 |
|
68 |
class ImageProcessor:
|
69 |
def __init__(self):
|
70 |
-
self.
|
71 |
-
|
72 |
-
|
73 |
-
"grain": self._apply_grain,
|
74 |
-
"hdr": self._apply_hdr,
|
75 |
-
"pixelation": self._apply_pixelation
|
76 |
-
}
|
77 |
-
|
78 |
-
def process_image(self, image: Image.Image, style_params: Dict) -> Image.Image:
|
79 |
try:
|
|
|
80 |
img_array = np.array(image)
|
81 |
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
return Image.fromarray(img_array)
|
|
|
87 |
except Exception as e:
|
88 |
logger.error(f"Erreur traitement: {str(e)}")
|
89 |
return image
|
90 |
-
|
91 |
-
def
|
92 |
hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
|
93 |
-
hsv[..., 1] = np.clip(hsv[..., 1] *
|
94 |
-
|
95 |
-
|
96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
def _apply_bokeh(self, image: np.ndarray, strength: float) -> np.ndarray:
|
98 |
blur = cv2.GaussianBlur(image, (0, 0), int(30 * strength))
|
99 |
mask = np.random.random(image.shape[:2]) > 0.5
|
100 |
result = image.copy()
|
101 |
result[mask] = blur[mask]
|
102 |
return result
|
103 |
-
|
104 |
-
def _apply_grain(self, image: np.ndarray, strength: float) -> np.ndarray:
|
105 |
-
noise = np.random.normal(0, strength * 50, image.shape).astype(np.uint8)
|
106 |
-
return np.clip(image + noise, 0, 255)
|
107 |
-
|
108 |
def _apply_hdr(self, image: np.ndarray, strength: float) -> np.ndarray:
|
109 |
return exposure.adjust_gamma(image, 1.0 / strength)
|
110 |
|
111 |
-
def _apply_pixelation(self, image: np.ndarray, strength: float) -> np.ndarray:
|
112 |
-
h, w = image.shape[:2]
|
113 |
-
size = int(max(h, w) * (1 - strength))
|
114 |
-
small = cv2.resize(image, (size, size), interpolation=cv2.INTER_LINEAR)
|
115 |
-
return cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)
|
116 |
-
|
117 |
class ImageGenerator:
|
118 |
def __init__(self):
|
119 |
self.processor = ImageProcessor()
|
120 |
self.api_url = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
|
|
|
|
125 |
try:
|
126 |
-
|
|
|
|
|
|
|
127 |
final_prompt = f"{prompt}, {style_info['prompt']}"
|
128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
response = requests.post(
|
130 |
self.api_url,
|
131 |
headers=self.headers,
|
132 |
-
json=
|
133 |
timeout=30
|
134 |
)
|
135 |
|
136 |
if response.status_code != 200:
|
137 |
return None, f"Erreur API: {response.status_code}"
|
138 |
-
|
|
|
139 |
image = Image.open(io.BytesIO(response.content))
|
140 |
-
processed = self.processor.process_image(image, style_info[
|
141 |
|
142 |
return processed, "✨ Génération réussie!"
|
|
|
143 |
except Exception as e:
|
144 |
-
logger.error(f"Erreur: {str(e)}")
|
145 |
return None, f"Erreur: {str(e)}"
|
146 |
finally:
|
147 |
gc.collect()
|
148 |
-
def create_interface():
|
149 |
-
generator = ImageGenerator()
|
150 |
|
|
|
|
|
|
|
151 |
with gr.Blocks() as demo:
|
152 |
gr.HTML("""
|
153 |
<div style='text-align: center; margin-bottom: 1rem'>
|
154 |
<h1>🎨 Equity Art Engine</h1>
|
155 |
-
<p>Générateur d'Images
|
156 |
</div>
|
157 |
""")
|
158 |
|
159 |
with gr.Row():
|
160 |
-
with gr.Column(
|
161 |
-
prompt = gr.Textbox(
|
162 |
-
label="Description",
|
163 |
-
placeholder="Décrivez votre image..."
|
164 |
-
)
|
165 |
|
166 |
style_category = gr.Dropdown(
|
167 |
-
choices=list(
|
168 |
-
label="Catégorie de Style"
|
169 |
-
value="Styles Traditionnels"
|
170 |
)
|
171 |
|
172 |
-
style_name = gr.Dropdown(
|
173 |
-
label="Style Spécifique"
|
174 |
-
)
|
175 |
|
|
|
176 |
def update_styles(category):
|
177 |
-
return gr.Dropdown.update(
|
178 |
-
choices=list(generator.styles[category].keys()) if category else []
|
179 |
-
)
|
180 |
-
|
181 |
-
style_category.change(
|
182 |
-
update_styles,
|
183 |
-
inputs=style_category,
|
184 |
-
outputs=style_name
|
185 |
-
)
|
186 |
-
|
187 |
-
advanced_params = gr.Checkbox(
|
188 |
-
label="Paramètres avancés",
|
189 |
-
value=False
|
190 |
-
)
|
191 |
-
|
192 |
-
with gr.Column(visible=False) as advanced_options:
|
193 |
-
quality = gr.Slider(
|
194 |
-
minimum=1,
|
195 |
-
maximum=10,
|
196 |
-
value=7,
|
197 |
-
step=1,
|
198 |
-
label="Qualité"
|
199 |
-
)
|
200 |
|
201 |
-
|
202 |
-
label="Seed (-1 pour aléatoire)",
|
203 |
-
value=-1
|
204 |
-
)
|
205 |
-
|
206 |
-
def toggle_advanced(show):
|
207 |
-
return gr.update(visible=show)
|
208 |
-
|
209 |
-
advanced_params.change(
|
210 |
-
toggle_advanced,
|
211 |
-
inputs=advanced_params,
|
212 |
-
outputs=advanced_options
|
213 |
-
)
|
214 |
|
215 |
-
generate_btn = gr.Button("✨ Générer"
|
216 |
|
217 |
-
with gr.Column(
|
218 |
-
|
219 |
-
|
220 |
|
221 |
-
def
|
222 |
if not all([prompt, category, style]):
|
223 |
return None, "⚠️ Veuillez remplir tous les champs requis"
|
224 |
|
225 |
-
|
226 |
-
"quality": quality if use_advanced else 7,
|
227 |
-
"seed": seed if use_advanced and seed != -1 else None
|
228 |
-
}
|
229 |
-
|
230 |
-
image, status_msg = generator.generate(
|
231 |
-
prompt=prompt,
|
232 |
-
style_category=category,
|
233 |
-
style_name=style
|
234 |
-
)
|
235 |
-
|
236 |
-
return image, status_msg
|
237 |
|
238 |
generate_btn.click(
|
239 |
-
|
240 |
-
inputs=[
|
241 |
-
|
242 |
-
style_category,
|
243 |
-
style_name,
|
244 |
-
advanced_params,
|
245 |
-
quality,
|
246 |
-
seed
|
247 |
-
],
|
248 |
-
outputs=[output_image, status]
|
249 |
)
|
250 |
|
251 |
return demo
|
|
|
12 |
import cv2
|
13 |
from skimage import exposure
|
14 |
import torch
|
15 |
+
import asyncio
|
16 |
|
17 |
logging.basicConfig(level=logging.DEBUG)
|
18 |
logger = logging.getLogger(__name__)
|
19 |
load_dotenv()
|
20 |
|
21 |
+
STYLES = {
|
22 |
+
"Styles Traditionnels": {
|
23 |
+
"Renaissance": {
|
24 |
+
"prompt": "renaissance masterpiece, anatomical precision, detailed texture, chiaroscuro lighting, oil painting technique, museum quality",
|
25 |
+
"negative_prompt": "modern, abstract, simple, digital",
|
26 |
+
"params": {"resolution": 4096, "detail_level": 0.95}
|
|
|
27 |
},
|
28 |
+
"Impressionnisme": {
|
29 |
+
"prompt": "impressionist style, visible brushstrokes, natural light, plein air painting",
|
30 |
+
"negative_prompt": "sharp, digital, modern",
|
31 |
+
"params": {"resolution": 3072, "noise_level": 0.3}
|
32 |
},
|
33 |
+
"Surréalisme": {
|
34 |
+
"prompt": "surrealist dreamlike scene, symbolic imagery, unconscious imagination",
|
35 |
+
"negative_prompt": "realistic, ordinary",
|
36 |
+
"params": {"resolution": 3072, "randomization": 0.4}
|
|
|
37 |
},
|
38 |
+
"Cubisme": {
|
39 |
+
"prompt": "cubist style, geometric forms, multiple perspectives, abstract interpretation",
|
40 |
+
"negative_prompt": "realistic, photographic",
|
41 |
+
"params": {"resolution": 2048, "geometric_strength": 0.8}
|
42 |
+
}
|
43 |
+
},
|
44 |
+
"Rendus Numériques": {
|
45 |
+
"Synthwave": {
|
46 |
+
"prompt": "synthwave aesthetic, neon grid, retro-futuristic, dramatic lighting, vibrant colors",
|
47 |
+
"negative_prompt": "natural, realistic, muted colors",
|
48 |
+
"params": {"saturation": 1.8, "neon_effect": True}
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
},
|
50 |
+
"Cyberpunk": {
|
51 |
+
"prompt": "cyberpunk style, neon-lit cityscape, high-tech, volumetric fog, futuristic",
|
52 |
+
"negative_prompt": "natural, vintage, rural",
|
53 |
+
"params": {"volumetric": True, "neon_intensity": 1.5}
|
54 |
+
}
|
55 |
+
},
|
56 |
+
"Photographie": {
|
57 |
+
"HDR": {
|
58 |
+
"prompt": "HDR photography, extreme dynamic range, rich details in shadows and highlights",
|
59 |
+
"negative_prompt": "flat lighting, low contrast",
|
60 |
+
"params": {"hdr_strength": 1.5, "resolution": 4096}
|
61 |
},
|
62 |
+
"Portrait Studio": {
|
63 |
+
"prompt": "professional studio portrait, perfect lighting, bokeh effect, sharp focus",
|
64 |
+
"negative_prompt": "blurry, noisy",
|
65 |
+
"params": {"bokeh": 0.7, "sharpness": 1.4}
|
66 |
}
|
67 |
}
|
68 |
+
}
|
69 |
|
70 |
class ImageProcessor:
|
71 |
def __init__(self):
|
72 |
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
73 |
+
|
74 |
+
def process_image(self, image: Image.Image, params: Dict) -> Image.Image:
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
try:
|
76 |
+
# Conversion en array numpy
|
77 |
img_array = np.array(image)
|
78 |
|
79 |
+
# Application des effets selon les paramètres
|
80 |
+
if params.get("neon_effect"):
|
81 |
+
img_array = self._apply_neon_effect(img_array)
|
82 |
+
|
83 |
+
if params.get("volumetric"):
|
84 |
+
img_array = self._apply_volumetric(img_array)
|
85 |
+
|
86 |
+
if params.get("bokeh"):
|
87 |
+
img_array = self._apply_bokeh(img_array, params["bokeh"])
|
88 |
+
|
89 |
+
if params.get("hdr_strength"):
|
90 |
+
img_array = self._apply_hdr(img_array, params["hdr_strength"])
|
91 |
+
|
92 |
return Image.fromarray(img_array)
|
93 |
+
|
94 |
except Exception as e:
|
95 |
logger.error(f"Erreur traitement: {str(e)}")
|
96 |
return image
|
97 |
+
|
98 |
+
def _apply_neon_effect(self, image: np.ndarray) -> np.ndarray:
|
99 |
hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
|
100 |
+
hsv[..., 1] = np.clip(hsv[..., 1] * 1.5, 0, 255) # Boost saturation
|
101 |
+
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
|
102 |
+
# Ajout de glow
|
103 |
+
blur = cv2.GaussianBlur(rgb, (0, 0), 15)
|
104 |
+
return cv2.addWeighted(rgb, 1, blur, 0.5, 0)
|
105 |
+
|
106 |
+
def _apply_volumetric(self, image: np.ndarray) -> np.ndarray:
|
107 |
+
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
|
108 |
+
fog = cv2.GaussianBlur(gray, (0, 0), 20)
|
109 |
+
return cv2.addWeighted(image, 1, cv2.cvtColor(fog, cv2.COLOR_GRAY2RGB), 0.2, 0)
|
110 |
+
|
111 |
def _apply_bokeh(self, image: np.ndarray, strength: float) -> np.ndarray:
|
112 |
blur = cv2.GaussianBlur(image, (0, 0), int(30 * strength))
|
113 |
mask = np.random.random(image.shape[:2]) > 0.5
|
114 |
result = image.copy()
|
115 |
result[mask] = blur[mask]
|
116 |
return result
|
117 |
+
|
|
|
|
|
|
|
|
|
118 |
def _apply_hdr(self, image: np.ndarray, strength: float) -> np.ndarray:
|
119 |
return exposure.adjust_gamma(image, 1.0 / strength)
|
120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
class ImageGenerator:
|
122 |
def __init__(self):
|
123 |
self.processor = ImageProcessor()
|
124 |
self.api_url = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
|
125 |
+
token = os.getenv('HUGGINGFACE_TOKEN')
|
126 |
+
if not token:
|
127 |
+
raise ValueError("HUGGINGFACE_TOKEN non trouvé dans les variables d'environnement!")
|
128 |
+
self.headers = {"Authorization": f"Bearer {token}"}
|
129 |
+
|
130 |
+
def generate(self, prompt: str, style_category: str, style_name: str) -> Tuple[Optional[Image.Image], str]:
|
131 |
try:
|
132 |
+
# Récupération du style
|
133 |
+
style_info = STYLES[style_category][style_name]
|
134 |
+
|
135 |
+
# Construction du prompt final
|
136 |
final_prompt = f"{prompt}, {style_info['prompt']}"
|
137 |
|
138 |
+
# Paramètres de génération
|
139 |
+
params = {
|
140 |
+
"inputs": final_prompt,
|
141 |
+
"negative_prompt": style_info["negative_prompt"],
|
142 |
+
"num_inference_steps": 50,
|
143 |
+
"guidance_scale": 7.5,
|
144 |
+
"width": style_info["params"].get("resolution", 1024),
|
145 |
+
"height": style_info["params"].get("resolution", 1024),
|
146 |
+
}
|
147 |
+
|
148 |
+
# Appel API
|
149 |
response = requests.post(
|
150 |
self.api_url,
|
151 |
headers=self.headers,
|
152 |
+
json=params,
|
153 |
timeout=30
|
154 |
)
|
155 |
|
156 |
if response.status_code != 200:
|
157 |
return None, f"Erreur API: {response.status_code}"
|
158 |
+
|
159 |
+
# Traitement de l'image
|
160 |
image = Image.open(io.BytesIO(response.content))
|
161 |
+
processed = self.processor.process_image(image, style_info["params"])
|
162 |
|
163 |
return processed, "✨ Génération réussie!"
|
164 |
+
|
165 |
except Exception as e:
|
166 |
+
logger.error(f"Erreur génération: {str(e)}")
|
167 |
return None, f"Erreur: {str(e)}"
|
168 |
finally:
|
169 |
gc.collect()
|
|
|
|
|
170 |
|
171 |
+
def create_interface():
|
172 |
+
generator = ImageGenerator()
|
173 |
+
|
174 |
with gr.Blocks() as demo:
|
175 |
gr.HTML("""
|
176 |
<div style='text-align: center; margin-bottom: 1rem'>
|
177 |
<h1>🎨 Equity Art Engine</h1>
|
178 |
+
<p>Générateur d'Images avec Styles Artistiques</p>
|
179 |
</div>
|
180 |
""")
|
181 |
|
182 |
with gr.Row():
|
183 |
+
with gr.Column():
|
184 |
+
prompt = gr.Textbox(label="Description", placeholder="Décrivez votre image...")
|
|
|
|
|
|
|
185 |
|
186 |
style_category = gr.Dropdown(
|
187 |
+
choices=list(STYLES.keys()),
|
188 |
+
label="Catégorie de Style"
|
|
|
189 |
)
|
190 |
|
191 |
+
style_name = gr.Dropdown(label="Style Spécifique")
|
|
|
|
|
192 |
|
193 |
+
# Mise à jour dynamique des styles
|
194 |
def update_styles(category):
|
195 |
+
return gr.Dropdown.update(choices=list(STYLES[category].keys()) if category else [])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
|
197 |
+
style_category.change(update_styles, inputs=style_category, outputs=style_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
|
199 |
+
generate_btn = gr.Button("✨ Générer")
|
200 |
|
201 |
+
with gr.Column():
|
202 |
+
image_output = gr.Image(label="Résultat")
|
203 |
+
status_output = gr.Textbox(label="Status")
|
204 |
|
205 |
+
def generate(prompt, category, style):
|
206 |
if not all([prompt, category, style]):
|
207 |
return None, "⚠️ Veuillez remplir tous les champs requis"
|
208 |
|
209 |
+
return generator.generate(prompt, category, style)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
|
211 |
generate_btn.click(
|
212 |
+
generate,
|
213 |
+
inputs=[prompt, style_category, style_name],
|
214 |
+
outputs=[image_output, status_output]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
)
|
216 |
|
217 |
return demo
|