Equityone commited on
Commit
ffa8ad6
·
verified ·
1 Parent(s): cf5b603

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +222 -248
app.py CHANGED
@@ -1,6 +1,6 @@
1
  import gradio as gr
2
  import os
3
- from PIL import Image, ImageEnhance
4
  import requests
5
  import io
6
  import gc
@@ -14,296 +14,270 @@ from skimage import exposure
14
  import torch
15
 
16
  # Configuration du logging
17
- logging.basicConfig(
18
- level=logging.DEBUG,
19
- format='%(asctime)s - %(levelname)s - %(message)s',
20
- handlers=[
21
- logging.FileHandler('equity_space.log'),
22
- logging.StreamHandler()
23
- ]
24
- )
25
  logger = logging.getLogger(__name__)
26
  load_dotenv()
27
 
28
- # Définition des styles Equity avec paramètres d'optimisation avancés
29
- ART_STYLES = {
30
- "Ultra Technique": {
31
- "prompt_prefix": """ultra detailed technical drawing, engineering precision,
32
- scientific accuracy, professional schematic, architectural detail, perfect measurements,
33
- masterful technical illustration, 8k UHD quality, professional engineering visualization""",
34
- "negative_prompt": "artistic, painterly, imprecise, blurry, sketchy",
35
- "quality_boost": 1.5,
36
- "image_params": {
37
  "sharpness": 1.4,
38
- "technical_detail": True,
39
  "contrast": 1.2,
40
- "denoise": 0.3
 
 
41
  }
42
  },
43
- "Innovation Moderne": {
44
- "prompt_prefix": """cutting-edge technological design, modern engineering aesthetic,
45
- innovative technical concept, futuristic schematic, professional industrial visualization,
46
- high-tech blueprint, advanced technical detail, ultra modern technical drawing""",
47
- "negative_prompt": "vintage, traditional, hand-drawn, artistic",
48
- "quality_boost": 1.4,
49
- "image_params": {
50
- "sharpness": 1.3,
51
- "contrast": 1.3,
52
- "clarity": True
53
  }
54
  },
55
- "Précision Scientifique": {
56
- "prompt_prefix": """scientific visualization, mathematical accuracy,
57
- precise technical detail, analytical illustration, professional documentation,
58
- engineering excellence, technical mastery, detailed scientific diagram""",
59
- "negative_prompt": "artistic interpretation, abstract, undefined",
60
- "quality_boost": 1.5,
61
- "image_params": {
62
- "detail_enhancement": True,
63
- "precision": 1.4,
64
- "clarity": True
65
  }
66
  },
67
- "Vision Futuriste": {
68
- "prompt_prefix": """future technology concept, advanced engineering design,
69
- next-generation technical visualization, innovative blueprint style,
70
- high-tech schematic art, professional futuristic documentation""",
71
- "negative_prompt": "retro, vintage, traditional technique",
72
- "quality_boost": 1.4,
73
- "image_params": {
74
- "tech_enhancement": True,
75
- "modern_look": True,
76
- "sharpness": 1.3
77
  }
78
  }
79
  }
80
 
81
- class ImageEnhancer:
82
- """Classe de traitement et d'amélioration d'image avancée"""
83
 
84
  def __init__(self):
85
  self.device = "cuda" if torch.cuda.is_available() else "cpu"
86
-
87
- def _apply_clahe(self, image: np.ndarray) -> np.ndarray:
88
- """Amélioration adaptative du contraste"""
89
- if len(image.shape) == 3:
90
- lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
91
- l, a, b = cv2.split(lab)
92
- clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
93
- l = clahe.apply(l)
94
- lab = cv2.merge((l,a,b))
95
- return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  return image
97
 
98
- def _enhance_details(self, image: np.ndarray) -> np.ndarray:
99
- """Amélioration des détails techniques"""
100
- kernel = np.array([[-1,-1,-1],
101
- [-1, 9,-1],
102
- [-1,-1,-1]])
103
- return cv2.filter2D(image, -1, kernel)
104
-
105
- def _reduce_noise(self, image: np.ndarray, strength: float = 0.3) -> np.ndarray:
106
- """Réduction du bruit adaptative"""
107
- return cv2.fastNlMeansDenoisingColored(
108
- image,
109
- None,
110
- h=10 * strength,
111
- hColor=10,
112
- templateWindowSize=7,
113
- searchWindowSize=21
114
- )
115
-
116
- def _adjust_gamma(self, image: np.ndarray, gamma: float = 1.0) -> np.ndarray:
117
- """Ajustement gamma pour l'optimisation des tons"""
118
- return exposure.adjust_gamma(image, gamma)
119
-
120
- def enhance_image(self, image: Image.Image, params: Dict) -> Image.Image:
121
- """Pipeline complet d'amélioration d'image"""
122
- # Conversion en format CV2
123
- cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
124
-
125
- # Application des améliorations selon les paramètres
126
- if params.get('technical_detail', False):
127
- cv_image = self._enhance_details(cv_image)
128
-
129
- if params.get('clarity', False):
130
- cv_image = self._apply_clahe(cv_image)
131
-
132
- if params.get('denoise', 0) > 0:
133
- cv_image = self._reduce_noise(cv_image, params['denoise'])
134
-
135
- if params.get('gamma', 1.0) != 1.0:
136
- cv_image = self._adjust_gamma(cv_image, params['gamma'])
137
-
138
- # Reconversion en format PIL
139
- return Image.fromarray(cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB))
140
-
141
  class ImageGenerator:
142
- """Générateur d'images principal avec optimisations Equity"""
143
-
144
  def __init__(self):
145
  self.API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
146
- self.enhancer = ImageEnhancer()
147
- token = os.getenv('HUGGINGFACE_TOKEN')
148
- if not token:
149
- logger.error("HUGGINGFACE_TOKEN non trouvé!")
150
- self.headers = {"Authorization": f"Bearer {token}"}
151
- logger.info("ImageGenerator initialisé")
152
-
153
- def _prepare_prompt(self, params: Dict[str, Any], style_info: Dict) -> str:
154
- """Prépare le prompt optimisé"""
155
- base_prompt = params['subject']
156
- if params.get('title'):
157
- base_prompt += f", with text: {params['title']}"
158
- return f"{base_prompt}, {style_info['prompt_prefix']}"
159
-
160
- def _get_generation_params(self, params: Dict[str, Any], style_info: Dict) -> Dict:
161
- """Configure les paramètres de génération optimaux"""
162
- return {
163
- "negative_prompt": style_info["negative_prompt"],
164
- "num_inference_steps": int(50 * style_info["quality_boost"]),
165
- "guidance_scale": min(9.0 * style_info["quality_boost"], 12.0),
166
- "width": 1024 if params.get("quality", 35) > 40 else 768,
167
- "height": 1024 if params["orientation"] == "Portrait" else 768
168
- }
169
-
170
  def generate(self, params: Dict[str, Any]) -> Tuple[Optional[Image.Image], str]:
171
- """Génération d'image avec optimisations Equity"""
172
  try:
173
- style_info = ART_STYLES.get(params["style"])
174
- if not style_info:
175
- return None, "⚠️ Style non trouvé"
176
-
177
- prompt = self._prepare_prompt(params, style_info)
178
- generation_params = self._get_generation_params(params, style_info)
179
-
180
- payload = {
181
- "inputs": prompt,
182
- "parameters": generation_params
 
183
  }
184
-
 
185
  response = requests.post(
186
  self.API_URL,
187
  headers=self.headers,
188
- json=payload,
189
- timeout=45
190
  )
191
-
192
- if response.status_code == 200:
193
- image = Image.open(io.BytesIO(response.content))
194
- enhanced_image = self.enhancer.enhance_image(
195
- image,
196
- style_info["image_params"]
197
- )
198
- return enhanced_image, "✨ Création réussie!"
199
- else:
200
- error_msg = f"⚠️ Erreur API {response.status_code}: {response.text}"
201
- logger.error(error_msg)
202
- return None, error_msg
203
-
 
 
204
  except Exception as e:
205
- error_msg = f"⚠️ Erreur: {str(e)}"
206
- logger.exception("Erreur génération:")
207
- return None, error_msg
208
  finally:
209
  gc.collect()
210
 
211
  def create_interface():
212
- """Création de l'interface utilisateur Equity"""
213
  generator = ImageGenerator()
214
-
215
  with gr.Blocks(css="style.css") as app:
216
  gr.HTML("""
217
- <div class="welcome">
218
- <h1>🎨 Equity Artisan 3.0</h1>
219
- <p>Assistant de Création d'Images Professionnelles</p>
220
  </div>
221
  """)
222
-
223
- with gr.Column(elem_classes="container"):
224
- with gr.Group(elem_classes="controls-group"):
225
- with gr.Row():
226
- format_size = gr.Dropdown(
227
- choices=["A4", "A3", "A2", "A1"],
228
- value="A4",
229
- label="Format"
230
- )
231
- orientation = gr.Radio(
232
- choices=["Portrait", "Paysage"],
233
- value="Portrait",
234
- label="Orientation"
235
- )
236
- style = gr.Dropdown(
237
- choices=list(ART_STYLES.keys()),
238
- value="Ultra Technique",
239
- label="Style Technique"
240
- )
241
-
242
- with gr.Group(elem_classes="controls-group"):
243
- subject = gr.Textbox(
244
- label="Description",
245
- placeholder="Décrivez votre vision technique...",
246
- lines=3
247
  )
248
- title = gr.Textbox(
249
- label="Titre (optionnel)",
250
- placeholder="Titre à inclure dans l'image..."
 
 
251
  )
252
-
253
- with gr.Group(elem_classes="controls-group"):
254
- with gr.Row():
255
- quality = gr.Slider(
256
- minimum=30,
257
- maximum=50,
258
- value=40,
259
- label="Qualité Technique"
260
- )
261
- detail_level = gr.Slider(
262
- minimum=1,
263
- maximum=10,
264
- value=8,
265
- step=1,
266
- label="Niveau de Détail"
267
- )
268
-
269
  with gr.Row():
270
- generate_btn = gr.Button(" Générer", variant="primary")
271
- clear_btn = gr.Button("🗑️ Effacer")
272
-
273
- image_output = gr.Image(label="Résultat")
274
- status = gr.Textbox(label="Status", interactive=False)
275
-
276
- def generate(*args):
277
- params = {
278
- "format_size": args[0],
279
- "orientation": args[1],
280
- "style": args[2],
281
- "subject": args[3],
282
- "title": args[4],
283
- "quality": args[5],
284
- "detail_level": args[6]
285
- }
286
- return generator.generate(params)
287
-
288
- generate_btn.click(
289
- generate,
290
- inputs=[
291
- format_size,
292
- orientation,
293
- style,
294
- subject,
295
- title,
296
- quality,
297
- detail_level
298
- ],
299
- outputs=[image_output, status]
300
- )
301
-
302
- clear_btn.click(
303
- lambda: (None, "🗑️ Image effacée"),
304
- outputs=[image_output, status]
305
- )
306
-
 
307
  return app
308
 
309
  if __name__ == "__main__":
 
1
  import gradio as gr
2
  import os
3
+ from PIL import Image, ImageEnhance, ImageFilter
4
  import requests
5
  import io
6
  import gc
 
14
  import torch
15
 
16
  # Configuration du logging
17
+ logging.basicConfig(level=logging.DEBUG)
 
 
 
 
 
 
 
18
  logger = logging.getLogger(__name__)
19
  load_dotenv()
20
 
21
+ # Configurations photographiques inspirées d'appareils emblématiques
22
+ PHOTO_STYLES = {
23
+ "Leica": {
24
+ "description": "Style Leica classique 35mm - Netteté exceptionnelle, contraste naturel",
25
+ "params": {
 
 
 
 
26
  "sharpness": 1.4,
 
27
  "contrast": 1.2,
28
+ "black_point": 5,
29
+ "white_point": 248,
30
+ "color_balance": "neutral"
31
  }
32
  },
33
+ "Hasselblad": {
34
+ "description": "Qualité moyen format - Détails riches, tons subtils",
35
+ "params": {
36
+ "sharpness": 1.6,
37
+ "contrast": 1.1,
38
+ "dynamic_range": "extended",
39
+ "color_depth": "high",
40
+ "medium_format": True
 
 
41
  }
42
  },
43
+ "Polaroid": {
44
+ "description": "Look vintage instantané - Tons chauds, vignettage doux",
45
+ "params": {
46
+ "vintage_effect": True,
47
+ "color_shift": "warm",
48
+ "vignette": 0.3,
49
+ "grain": "fine"
 
 
 
50
  }
51
  },
52
+ "Fujifilm": {
53
+ "description": "Rendu Fujifilm - Couleurs riches, tons chair naturels",
54
+ "params": {
55
+ "color_profile": "fuji",
56
+ "skin_tones": "enhanced",
57
+ "dynamic_range": "wide",
58
+ "film_simulation": True
 
 
 
59
  }
60
  }
61
  }
62
 
63
+ class ImageProcessor:
64
+ """Processeur d'image avec simulations d'appareils photos classiques"""
65
 
66
  def __init__(self):
67
  self.device = "cuda" if torch.cuda.is_available() else "cpu"
68
+
69
+ def apply_photo_style(self, image: Image.Image, style: str) -> Image.Image:
70
+ """Applique un style photographique inspiré d'appareils classiques"""
71
+ style_params = PHOTO_STYLES[style]["params"]
72
+
73
+ # Création d'une copie de l'image pour le traitement
74
+ processed = image.copy()
75
+
76
+ # Application des paramètres de base
77
+ if style_params.get("sharpness"):
78
+ processed = ImageEnhance.Sharpness(processed).enhance(style_params["sharpness"])
79
+
80
+ if style_params.get("contrast"):
81
+ processed = ImageEnhance.Contrast(processed).enhance(style_params["contrast"])
82
+
83
+ # Effets spéciaux selon le style
84
+ if style_params.get("vintage_effect"):
85
+ processed = self._apply_vintage_effect(processed)
86
+
87
+ if style_params.get("medium_format"):
88
+ processed = self._simulate_medium_format(processed)
89
+
90
+ if style_params.get("vignette"):
91
+ processed = self._add_vignette(processed, style_params["vignette"])
92
+
93
+ return processed
94
+
95
+ def _apply_vintage_effect(self, image: Image.Image) -> Image.Image:
96
+ """Simule l'effet vintage type Polaroid"""
97
+ # Conversion en numpy array pour le traitement
98
+ img_array = np.array(image)
99
+
100
+ # Ajustement des courbes de couleur
101
+ img_array = exposure.adjust_gamma(img_array, 1.2)
102
+
103
+ # Ajout de grain subtil
104
+ noise = np.random.normal(0, 2, img_array.shape)
105
+ img_array = np.clip(img_array + noise, 0, 255).astype(np.uint8)
106
+
107
+ # Léger flou pour adoucir
108
+ return Image.fromarray(img_array).filter(ImageFilter.GaussianBlur(0.5))
109
+
110
+ def _simulate_medium_format(self, image: Image.Image) -> Image.Image:
111
+ """Simule le rendu moyen format type Hasselblad"""
112
+ # Augmentation de la netteté et du micro-contraste
113
+ enhanced = ImageEnhance.Sharpness(image).enhance(1.6)
114
+
115
+ # Optimisation de la plage dynamique
116
+ img_array = np.array(enhanced)
117
+ img_array = exposure.equalize_adapthist(img_array, clip_limit=0.02)
118
+
119
+ return Image.fromarray((img_array * 255).astype(np.uint8))
120
+
121
+ def _add_vignette(self, image: Image.Image, strength: float) -> Image.Image:
122
+ """Ajoute un effet de vignettage"""
123
+ # Création du masque de vignettage
124
+ mask = Image.new('L', image.size, 255)
125
+ draw = ImageDraw.Draw(mask)
126
+
127
+ width, height = image.size
128
+ for i in range(min(width, height) // 2):
129
+ alpha = int(255 * (1 - i * strength / (min(width, height) // 2)))
130
+ draw.ellipse([i, i, width-i, height-i], fill=alpha)
131
+
132
+ # Application du masque
133
+ image.putalpha(mask)
134
  return image
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  class ImageGenerator:
137
+ """Générateur d'images avec confirmation et styles photographiques"""
138
+
139
  def __init__(self):
140
  self.API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
141
+ self.processor = ImageProcessor()
142
+ self.headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_TOKEN')}"}
143
+
144
+ async def confirm_generation(self, params: Dict[str, Any]) -> bool:
145
+ """Demande confirmation avant génération"""
146
+ style_info = PHOTO_STYLES[params["photo_style"]]
147
+ confirmation_message = f"""
148
+ 🎨 Paramètres de génération :
149
+ - Style photo : {params['photo_style']} ({style_info['description']})
150
+ - Description : {params['prompt']}
151
+ - Format : {params['orientation']}
152
+
153
+ Voulez-vous continuer ?
154
+ """
155
+ return await self._show_confirmation_dialog(confirmation_message)
156
+
 
 
 
 
 
 
 
 
157
  def generate(self, params: Dict[str, Any]) -> Tuple[Optional[Image.Image], str]:
158
+ """Génération d'image avec style photographique"""
159
  try:
160
+ if not await self.confirm_generation(params):
161
+ return None, "Génération annulée par l'utilisateur"
162
+
163
+ # Configuration de la requête
164
+ generation_params = {
165
+ "prompt": params["prompt"],
166
+ "negative_prompt": "low quality, blurry, distorted",
167
+ "num_inference_steps": 50,
168
+ "guidance_scale": 7.5,
169
+ "width": 1024 if params["orientation"] == "landscape" else 768,
170
+ "height": 768 if params["orientation"] == "landscape" else 1024
171
  }
172
+
173
+ # Génération via API
174
  response = requests.post(
175
  self.API_URL,
176
  headers=self.headers,
177
+ json={"inputs": generation_params["prompt"], "parameters": generation_params},
178
+ timeout=30
179
  )
180
+
181
+ if response.status_code != 200:
182
+ return None, f"Erreur API: {response.status_code}"
183
+
184
+ # Traitement de l'image
185
+ image = Image.open(io.BytesIO(response.content))
186
+
187
+ # Application du style photographique
188
+ processed_image = self.processor.apply_photo_style(
189
+ image,
190
+ params["photo_style"]
191
+ )
192
+
193
+ return processed_image, "✨ Génération réussie"
194
+
195
  except Exception as e:
196
+ logger.error(f"Erreur lors de la génération: {str(e)}")
197
+ return None, f"Erreur: {str(e)}"
 
198
  finally:
199
  gc.collect()
200
 
201
  def create_interface():
202
+ """Création de l'interface utilisateur avec confirmation"""
203
  generator = ImageGenerator()
204
+
205
  with gr.Blocks(css="style.css") as app:
206
  gr.HTML("""
207
+ <div class="header">
208
+ <h1>🎨 Equity Art Studio Pro</h1>
209
+ <p>Création d'images avec styles photographiques classiques</p>
210
  </div>
211
  """)
212
+
213
+ with gr.Column():
214
+ # Contrôles de base
215
+ prompt = gr.Textbox(
216
+ label="Description",
217
+ placeholder="Décrivez l'image souhaitée..."
218
+ )
219
+
220
+ with gr.Row():
221
+ orientation = gr.Radio(
222
+ choices=["portrait", "landscape", "square"],
223
+ value="portrait",
224
+ label="Format"
 
 
 
 
 
 
 
 
 
 
 
 
225
  )
226
+
227
+ photo_style = gr.Dropdown(
228
+ choices=list(PHOTO_STYLES.keys()),
229
+ value="Leica",
230
+ label="Style Photographique"
231
  )
232
+
233
+ # Description du style sélectionné
234
+ style_info = gr.HTML()
235
+
236
+ def update_style_info(style):
237
+ return f"<div class='style-info'>{PHOTO_STYLES[style]['description']}</div>"
238
+
239
+ photo_style.change(update_style_info, inputs=[photo_style], outputs=[style_info])
240
+
241
+ # Boutons
 
 
 
 
 
 
 
242
  with gr.Row():
243
+ generate_btn = gr.Button("🎨 Prévisualiser", variant="secondary")
244
+ confirm_btn = gr.Button(" Générer", variant="primary")
245
+
246
+ # Sorties
247
+ preview = gr.Image(label="Prévisualisation", visible=False)
248
+ result = gr.Image(label="Résultat")
249
+ status = gr.Textbox(label="Status")
250
+
251
+ # Logique de génération
252
+ def preview_params(prompt, orientation, style):
253
+ return f"""
254
+ <div class='preview'>
255
+ <h3>Paramètres de génération</h3>
256
+ <ul>
257
+ <li>Description : {prompt}</li>
258
+ <li>Format : {orientation}</li>
259
+ <li>Style : {style}</li>
260
+ </ul>
261
+ <p>Confirmez pour lancer la génération.</p>
262
+ </div>
263
+ """
264
+
265
+ generate_btn.click(
266
+ preview_params,
267
+ inputs=[prompt, orientation, photo_style],
268
+ outputs=[preview]
269
+ )
270
+
271
+ confirm_btn.click(
272
+ generator.generate,
273
+ inputs={
274
+ "prompt": prompt,
275
+ "orientation": orientation,
276
+ "photo_style": photo_style
277
+ },
278
+ outputs=[result, status]
279
+ )
280
+
281
  return app
282
 
283
  if __name__ == "__main__":