Equityone commited on
Commit
4367472
·
verified ·
1 Parent(s): 8527b54

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +294 -168
app.py CHANGED
@@ -1,221 +1,347 @@
1
  import gradio as gr
2
  import os
3
- from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter
4
  import requests
5
  import io
6
  import gc
7
  import json
8
- from typing import Tuple, Optional, Dict, Any, List
9
  import logging
10
  from dotenv import load_dotenv
11
- import numpy as np
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
 
218
 
219
  if __name__ == "__main__":
220
- demo = create_interface()
221
- demo.launch()
 
 
1
  import gradio as gr
2
  import os
3
+ from PIL import Image
4
  import requests
5
  import io
6
  import gc
7
  import json
8
+ from typing import Tuple, Optional, Dict, Any
9
  import logging
10
  from dotenv import load_dotenv
 
 
 
 
 
11
 
12
+ # Configuration du logging
13
+ logging.basicConfig(
14
+ level=logging.DEBUG,
15
+ format='%(asctime)s - %(levelname)s - %(message)s'
16
+ )
17
  logger = logging.getLogger(__name__)
18
+
19
+ # Chargement des variables d'environnement
20
  load_dotenv()
21
 
22
+ # Styles artistiques étendus
23
+ ART_STYLES = {
24
+ "Art Moderne": {
25
+ "prompt_prefix": "modern art style poster, professional design",
26
+ "negative_prompt": "traditional, photorealistic, cluttered, busy design"
27
+ },
28
+ "Neo Vintage": {
29
+ "prompt_prefix": "vintage style advertising poster, retro design",
30
+ "negative_prompt": "modern, digital, contemporary style"
31
+ },
32
+ "Pop Art": {
33
+ "prompt_prefix": "pop art style poster, bold design",
34
+ "negative_prompt": "subtle, realistic, traditional art"
35
+ },
36
+ "Minimaliste": {
37
+ "prompt_prefix": "minimalist design poster, clean composition",
38
+ "negative_prompt": "complex, detailed, ornate, busy"
 
 
 
 
 
39
  },
40
+ "Cyberpunk": {
41
+ "prompt_prefix": "cyberpunk style poster, neon lights, futuristic design",
42
+ "negative_prompt": "vintage, natural, rustic, traditional"
 
 
 
 
 
 
 
 
43
  },
44
+ "Aquarelle": {
45
+ "prompt_prefix": "watercolor art style poster, fluid artistic design",
46
+ "negative_prompt": "digital, sharp, photorealistic"
47
+ },
48
+ "Art Déco": {
49
+ "prompt_prefix": "art deco style poster, geometric patterns, luxury design",
50
+ "negative_prompt": "modern, minimalist, casual"
51
+ },
52
+ "Japonais": {
53
+ "prompt_prefix": "japanese art style poster, ukiyo-e inspired design",
54
+ "negative_prompt": "western, modern, photographic"
55
  }
56
  }
57
 
58
+ # Paramètres de composition
59
+ COMPOSITION_PARAMS = {
60
+ "Layouts": {
61
+ "Centré": "centered composition, balanced layout",
62
+ "Asymétrique": "dynamic asymmetrical composition",
63
+ "Grille": "grid-based layout, structured composition",
64
+ "Diagonal": "diagonal dynamic composition",
65
+ "Minimaliste": "minimal composition, lots of whitespace"
66
+ },
67
+ "Ambiances": {
68
+ "Dramatique": "dramatic lighting, high contrast",
69
+ "Doux": "soft lighting, gentle atmosphere",
70
+ "Vibrant": "vibrant colors, energetic mood",
71
+ "Mystérieux": "mysterious atmosphere, moody lighting",
72
+ "Serein": "peaceful atmosphere, calm mood"
73
+ },
74
+ "Palette": {
75
+ "Monochrome": "monochromatic color scheme",
76
+ "Contrasté": "high contrast color palette",
77
+ "Pastel": "soft pastel color palette",
78
+ "Terre": "earthy color palette",
79
+ "Néon": "neon color palette"
80
+ }
81
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  class ImageGenerator:
84
  def __init__(self):
85
+ self.API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
 
86
  token = os.getenv('HUGGINGFACE_TOKEN')
87
  if not token:
88
+ logger.error("HUGGINGFACE_TOKEN non trouvé!")
89
  self.headers = {"Authorization": f"Bearer {token}"}
90
+ logger.info("ImageGenerator initialisé")
91
+
92
+ def _build_prompt(self, params: Dict[str, Any]) -> str:
93
+ """Construction de prompt améliorée"""
94
+ style_info = ART_STYLES.get(params["style"], ART_STYLES["Neo Vintage"])
95
+ prompt = f"{style_info['prompt_prefix']}, {params['subject']}"
96
+
97
+ # Ajout des paramètres de composition
98
+ if params.get("layout"):
99
+ prompt += f", {COMPOSITION_PARAMS['Layouts'][params['layout']]}"
100
+ if params.get("ambiance"):
101
+ prompt += f", {COMPOSITION_PARAMS['Ambiances'][params['ambiance']]}"
102
+ if params.get("palette"):
103
+ prompt += f", {COMPOSITION_PARAMS['Palette'][params['palette']]}"
104
+
105
+ # Ajout des ajustements fins
106
+ if params.get("detail_level"):
107
+ detail_strength = params["detail_level"]
108
+ prompt += f", {'highly detailed' if detail_strength > 7 else 'moderately detailed'}"
109
+
110
+ if params.get("contrast"):
111
+ contrast_strength = params["contrast"]
112
+ prompt += f", {'high contrast' if contrast_strength > 7 else 'balanced contrast'}"
113
+
114
+ if params.get("saturation"):
115
+ saturation_strength = params["saturation"]
116
+ prompt += f", {'vibrant colors' if saturation_strength > 7 else 'subtle colors'}"
117
+
118
+ if params.get("title"):
119
+ prompt += f", with text saying '{params['title']}'"
120
+
121
+ logger.debug(f"Prompt final: {prompt}")
122
+ return prompt
123
+
124
+ def generate(self, params: Dict[str, Any]) -> Tuple[Optional[Image.Image], str]:
125
  try:
126
+ logger.info(f"Début de génération avec paramètres: {json.dumps(params, indent=2)}")
 
127
 
128
+ if 'Bearer None' in self.headers['Authorization']:
129
+ return None, "⚠️ Erreur: Token Hugging Face non configuré"
130
+
131
+ prompt = self._build_prompt(params)
132
 
133
+ # Configuration de base
134
+ payload = {
135
+ "inputs": prompt,
136
+ "parameters": {
137
+ "negative_prompt": ART_STYLES[params["style"]]["negative_prompt"],
138
+ "num_inference_steps": min(int(35 * (params["quality"]/100)), 40),
139
+ "guidance_scale": min(7.5 * (params["creativity"]/10), 10.0),
140
+ "width": 768,
141
+ "height": 768 if params["orientation"] == "Portrait" else 512
142
+ }
143
  }
144
 
145
+ logger.debug(f"Payload: {json.dumps(payload, indent=2)}")
146
+
147
  response = requests.post(
148
+ self.API_URL,
149
  headers=self.headers,
150
+ json=payload,
151
  timeout=30
152
  )
153
 
154
+ if response.status_code == 200:
155
+ image = Image.open(io.BytesIO(response.content))
156
+ return image, "✨ Création réussie!"
157
+ else:
158
+ error_msg = f"⚠️ Erreur API {response.status_code}: {response.text}"
159
+ logger.error(error_msg)
160
+ return None, error_msg
161
+
 
162
  except Exception as e:
163
+ error_msg = f"⚠️ Erreur: {str(e)}"
164
+ logger.exception("Erreur pendant la génération:")
165
+ return None, error_msg
166
  finally:
167
  gc.collect()
168
 
169
  def create_interface():
170
+ logger.info("Création de l'interface Gradio")
171
+
172
+ css = """
173
+ .container { max-width: 1200px; margin: auto; }
174
+ .welcome { text-align: center; margin: 20px 0; padding: 20px; background: #1e293b; border-radius: 10px; }
175
+ .controls-group { background: #2d3748; padding: 15px; border-radius: 5px; margin: 10px 0; }
176
+ .advanced-controls { background: #374151; padding: 12px; border-radius: 5px; margin: 8px 0; }
177
+ """
178
+
179
  generator = ImageGenerator()
180
 
181
+ with gr.Blocks(css=css) as app:
182
  gr.HTML("""
183
+ <div class="welcome">
184
+ <h1>🎨 Equity Artisan 3.0</h1>
185
+ <p>Assistant de création d'affiches professionnelles</p>
186
  </div>
187
  """)
188
 
189
+ with gr.Column(elem_classes="container"):
190
+ # Format et Orientation
191
+ with gr.Group(elem_classes="controls-group"):
192
+ gr.Markdown("### 📐 Format et Orientation")
193
+ with gr.Row():
194
+ format_size = gr.Dropdown(
195
+ choices=["A4", "A3", "A2", "A1", "A0"],
196
+ value="A4",
197
+ label="Format"
198
+ )
199
+ orientation = gr.Radio(
200
+ choices=["Portrait", "Paysage"],
201
+ value="Portrait",
202
+ label="Orientation"
203
+ )
204
+
205
+ # Style et Composition
206
+ with gr.Group(elem_classes="controls-group"):
207
+ gr.Markdown("### 🎨 Style et Composition")
208
+ with gr.Row():
209
+ style = gr.Dropdown(
210
+ choices=list(ART_STYLES.keys()),
211
+ value="Neo Vintage",
212
+ label="Style artistique"
213
+ )
214
+ layout = gr.Dropdown(
215
+ choices=list(COMPOSITION_PARAMS["Layouts"].keys()),
216
+ value="Centré",
217
+ label="Composition"
218
+ )
219
+
220
+ with gr.Row():
221
+ ambiance = gr.Dropdown(
222
+ choices=list(COMPOSITION_PARAMS["Ambiances"].keys()),
223
+ value="Dramatique",
224
+ label="Ambiance"
225
+ )
226
+ palette = gr.Dropdown(
227
+ choices=list(COMPOSITION_PARAMS["Palette"].keys()),
228
+ value="Contrasté",
229
+ label="Palette"
230
+ )
231
+
232
+ # Contenu
233
+ with gr.Group(elem_classes="controls-group"):
234
+ gr.Markdown("### 📝 Contenu")
235
+ subject = gr.Textbox(
236
+ label="Description",
237
+ placeholder="Décrivez votre vision..."
238
  )
239
+ title = gr.Textbox(
240
+ label="Titre",
241
+ placeholder="Titre de l'affiche..."
242
+ )
243
+
244
+ # Ajustements fins
245
+ with gr.Group(elem_classes="advanced-controls"):
246
+ gr.Markdown("### 🎯 Ajustements Fins")
247
+ with gr.Row():
248
+ detail_level = gr.Slider(
249
+ minimum=1,
250
+ maximum=10,
251
+ value=7,
252
+ step=1,
253
+ label="Niveau de Détail"
254
+ )
255
+ contrast = gr.Slider(
256
+ minimum=1,
257
+ maximum=10,
258
+ value=5,
259
+ step=1,
260
+ label="Contraste"
261
+ )
262
+ saturation = gr.Slider(
263
+ minimum=1,
264
+ maximum=10,
265
+ value=5,
266
+ step=1,
267
+ label="Saturation"
268
+ )
269
+
270
+ # Paramètres de génération
271
+ with gr.Group(elem_classes="controls-group"):
272
+ with gr.Row():
273
+ quality = gr.Slider(
274
+ minimum=30,
275
+ maximum=50,
276
+ value=35,
277
+ label="Qualité"
278
+ )
279
+ creativity = gr.Slider(
280
+ minimum=5,
281
+ maximum=15,
282
+ value=7.5,
283
+ label="Créativité"
284
+ )
285
 
286
+ # Boutons
287
+ with gr.Row():
288
+ generate_btn = gr.Button("✨ Générer", variant="primary")
289
+ clear_btn = gr.Button("🗑️ Effacer", variant="secondary")
290
+
291
+ # Sortie
292
+ image_output = gr.Image(label="Aperçu")
293
+ status = gr.Textbox(label="Statut", interactive=False)
294
+
295
+ def generate(*args):
296
+ logger.info("Démarrage d'une nouvelle génération")
297
+ params = {
298
+ "format_size": args[0],
299
+ "orientation": args[1],
300
+ "style": args[2],
301
+ "layout": args[3],
302
+ "ambiance": args[4],
303
+ "palette": args[5],
304
+ "subject": args[6],
305
+ "title": args[7],
306
+ "detail_level": args[8],
307
+ "contrast": args[9],
308
+ "saturation": args[10],
309
+ "quality": args[11],
310
+ "creativity": args[12]
311
+ }
312
+ result = generator.generate(params)
313
+ logger.info(f"Génération terminée avec statut: {result[1]}")
314
+ return result
315
 
316
  generate_btn.click(
317
  generate,
318
+ inputs=[
319
+ format_size,
320
+ orientation,
321
+ style,
322
+ layout,
323
+ ambiance,
324
+ palette,
325
+ subject,
326
+ title,
327
+ detail_level,
328
+ contrast,
329
+ saturation,
330
+ quality,
331
+ creativity
332
+ ],
333
+ outputs=[image_output, status]
334
+ )
335
+
336
+ clear_btn.click(
337
+ lambda: (None, "🗑️ Image effacée"),
338
+ outputs=[image_output, status]
339
  )
340
 
341
+ logger.info("Interface créée avec succès")
342
+ return app
343
 
344
  if __name__ == "__main__":
345
+ app = create_interface()
346
+ logger.info("Démarrage de l'application")
347
+ app.launch()