Chingkheinganba commited on
Commit
d408675
·
verified ·
1 Parent(s): 8cf49d0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -325
app.py CHANGED
@@ -1,348 +1,106 @@
1
- import gradio as gr
2
- import requests
3
  import os
4
- from PIL import Image, ImageFilter, ImageDraw, ImageFont
5
  import io
6
-
7
- # =========================================================
8
- # Configuration
9
- # =========================================================
10
- HF_TOKEN = os.getenv("HF_TOKEN")
11
- GEN_MODEL_ID = "runwayml/stable-diffusion-v1-5"
12
- GEN_API_URL = f"https://api-inference.huggingface.co/models/{GEN_MODEL_ID}?wait_for_model=true"
13
- GEN_STATUS_URL = f"https://api-inference.huggingface.co/status/{GEN_MODEL_ID}"
14
-
15
- # Architectural styles
16
- ARCH_STYLES = [
17
- "Gothic Cathedral", "Japanese Zen", "Modern Minimalist",
18
- "Medieval Castle", "Art Deco", "Chinese Palace",
19
- "Brutalist", "Victorian", "Mediterranean", "Futuristic"
 
 
 
 
 
20
  ]
21
 
22
- # =========================================================
23
- # Image utilities
24
- # =========================================================
25
- def create_building_analysis(input_image: Image.Image) -> Image.Image:
26
- """Create a simple 'edge map' structure analysis visualization."""
27
- if input_image is None:
28
- return create_info_image("Upload a building photo to start")
29
-
30
- try:
31
- img = input_image.resize((512, 512))
32
- grayscale = img.convert('L')
33
-
34
- # Edge detection and threshold
35
- edges = grayscale.filter(ImageFilter.FIND_EDGES)
36
- edges = edges.point(lambda x: 255 if x > 25 else 0)
37
-
38
- # Blue edges on black background
39
- analysis_img = Image.new('RGB', (512, 512), 'black')
40
- edge_pixels = edges.load()
41
- analysis_pixels = analysis_img.load()
42
-
43
- for i in range(512):
44
- for j in range(512):
45
- if edge_pixels[i, j] > 50:
46
- analysis_pixels[i, j] = (0, 150, 255) # Blue
47
-
48
- return analysis_img
49
-
50
- except Exception as e:
51
- return create_info_image(f"Analysis error: {str(e)}")
52
-
53
-
54
- def create_demo_building(style_name: str) -> Image.Image:
55
- """Create a decorative demo image when API is unavailable or generation fails."""
56
- colors = {
57
- "Gothic Cathedral": ('#2C3E50', '#E74C3C', '#ECF0F1'),
58
- "Japanese Zen": ('#8B4513', '#2E8B57', '#F5F5DC'),
59
- "Modern Minimalist": ('#FFFFFF', '#3498DB', '#2C3E50'),
60
- "Medieval Castle": ('#7F8C8D', '#C0392B', '#34495E'),
61
- "Art Deco": ('#D4AF37', '#000000', '#FFFFFF'),
62
- "Chinese Palace": ('#C0392B', '#F1C40F', '#2C3E50'),
63
- "Brutalist": ('#95A5A6', '#7F8C8D', '#34495E'),
64
- "Victorian": ('#E6B0AA', '#F1948A', '#2C3E50'),
65
- "Mediterranean": ('#E67E22', '#3498DB', '#F1C40F'),
66
- "Futuristic": ('#1ABC9C', '#9B59B6', '#34495E'),
67
- "Upload a photo to see magic!": ('#3498DB', '#2C3E50', '#ECF0F1')
68
- }
69
-
70
- bg_color, building_color, accent_color = colors.get(
71
- style_name, ('#3498DB', '#2C3E50', '#ECF0F1')
72
- )
73
-
74
- img = Image.new('RGB', (512, 512), bg_color)
75
- draw = ImageDraw.Draw(img)
76
-
77
- # Gradient background
78
- for y in range(512):
79
- if bg_color == '#FFFFFF': # Modern Minimalist special case
80
- shade = int(200 + (y / 512) * 55)
81
- draw.line([(0, y), (512, y)], fill=(shade, shade, shade))
82
  else:
83
- r, g, b = int(bg_color[1:3], 16), int(bg_color[3:5], 16), int(bg_color[5:7], 16)
84
- new_r = max(0, min(255, r + (y // 20)))
85
- new_g = max(0, min(255, g + (y // 20)))
86
- new_b = max(0, min(255, b + (y // 20)))
87
- draw.line([(0, y), (512, y)], fill=(new_r, new_g, new_b))
88
-
89
- # Building body
90
- draw.rectangle([150, 200, 362, 450], fill=building_color, outline=accent_color, width=3)
91
-
92
- # Windows
93
- window_colors = ['#F1C40F', '#E74C3C', '#1ABC9C', '#9B59B6']
94
- for i, color in enumerate(window_colors):
95
- x_pos = 170 + (i % 3) * 70
96
- y_pos = 230 + (i // 3) * 70
97
- draw.rectangle([x_pos, y_pos, x_pos + 40, y_pos + 50], fill=color)
98
-
99
- # Roof
100
- if "Gothic" in style_name:
101
- draw.polygon([(150, 200), (256, 150), (362, 200)], fill=accent_color)
102
- elif "Japanese" in style_name:
103
- draw.polygon([(150, 200), (256, 170), (362, 200)], fill=accent_color)
104
- else:
105
- draw.rectangle([150, 190, 362, 200], fill=accent_color)
106
-
107
- # Titles
108
- try:
109
- font = ImageFont.load_default()
110
- text = f"{style_name}"
111
- bbox = draw.textbbox((0, 0), text, font=font)
112
- text_width = bbox[2] - bbox[0]
113
- x = (512 - text_width) // 2
114
- draw.text((x, 50), text, fill='white', stroke_width=2, stroke_fill='black', font=font)
115
-
116
- if "Upload" not in style_name:
117
- draw.text((180, 470), "Architectural AI Demo", fill='white', font=font)
118
- except Exception:
119
- draw.text((200, 50), style_name, fill='white')
120
-
121
  return img
122
 
123
 
124
- def create_info_image(message: str) -> Image.Image:
125
- """Create a plain info panel image with wrapped text."""
126
- img = Image.new('RGB', (512, 512), color='#2C3E50')
127
- draw = ImageDraw.Draw(img)
128
- try:
129
- font = ImageFont.load_default()
130
- words = message.split(' ')
131
- current_line = ""
132
- y_text = 200
133
-
134
- for word in words:
135
- test_line = current_line + word + " "
136
- bbox = draw.textbbox((0, 0), test_line, font=font)
137
- text_width = bbox[2] - bbox[0]
138
-
139
- if text_width < 500:
140
- current_line = test_line
141
- else:
142
- draw.text((10, y_text), current_line, fill='white', font=font)
143
- y_text += 30
144
- current_line = word + " "
145
-
146
- if current_line:
147
- draw.text((10, y_text), current_line, fill='white', font=font)
148
- except Exception:
149
- draw.text((50, 250), message, fill='white')
150
-
151
- return img
152
-
153
- # =========================================================
154
- # HF API helpers
155
- # =========================================================
156
- def test_huggingface_api() -> str:
157
- """Return a single string suitable for a Textbox with model status."""
158
- if not HF_TOKEN:
159
- return "❌ No HF_TOKEN found in environment variables."
160
-
161
- headers = {"Authorization": f"Bearer {HF_TOKEN}"}
162
- try:
163
- r = requests.get(GEN_STATUS_URL, headers=headers, timeout=10)
164
- if r.status_code == 200:
165
- j = r.json()
166
- loaded = j.get("loaded")
167
- state = j.get("state")
168
- qlen = (j.get("queue") or {}).get("length")
169
- return f"✅ API reachable. loaded={loaded}, state={state}, queue_length={qlen}"
170
- elif r.status_code == 404:
171
- return "❌ Model not found or gated (check access to runwayml/stable-diffusion-v1-5)."
172
- else:
173
- return f"❌ API error {r.status_code}: {r.text[:180]}"
174
- except Exception as e:
175
- return f"❌ API test error: {e}"
176
 
 
 
 
 
 
177
 
178
- def generate_with_api(style_name: str):
179
- """Try to generate image via HF Inference API. Returns PIL.Image or None."""
180
  if not HF_TOKEN:
181
- return None
182
-
183
- prompt = f"architectural rendering of a {style_name.lower()} building, professional photography"
184
- headers = {
185
- "Authorization": f"Bearer {HF_TOKEN}",
186
- "Accept": "image/png"
187
- }
188
- payload = {
189
- "inputs": prompt,
190
- "parameters": {
191
- "guidance_scale": 7.5,
192
- "num_inference_steps": 20
193
- # Width/height may not be supported across all deployments:
194
- # "width": 512,
195
- # "height": 512
196
- }
197
- }
198
 
199
  try:
200
- response = requests.post(GEN_API_URL, headers=headers, json=payload, timeout=60)
201
- ctype = response.headers.get("content-type", "")
202
- if response.status_code == 200 and ctype.startswith("image/"):
203
- return Image.open(io.BytesIO(response.content))
204
- elif response.status_code == 503:
205
- # Model loading or warming up
206
- return None
207
- else:
208
- # Likely JSON error; log a snippet
209
- print(f"❌ API error {response.status_code}: {response.text[:200]}")
210
- return None
211
- except Exception as e:
212
- print(f"❌ Request error: {e}")
213
- return None
214
-
215
- # =========================================================
216
- # Main pipeline
217
- # =========================================================
218
- def generate_architectural_remix(original_image, style_choice):
219
- """Pipeline: test API → analyze structure → generate or show demo."""
220
- if original_image is None:
221
- analysis_img = create_info_image("Please upload a building photo")
222
- status = "Waiting for building photo..."
223
- result_img = create_demo_building("Upload a photo to see magic!")
224
- return analysis_img, status, result_img
225
-
226
- # Always keep a safe default analysis ready
227
- safe_analysis = create_info_image("Analysis unavailable")
228
-
229
- try:
230
- # 1) API status (returns a string now)
231
- api_status = test_huggingface_api()
232
- api_working = api_status.startswith("✅")
233
-
234
- # 2) Structure analysis (guard any failure)
235
- try:
236
- building_analysis = create_building_analysis(original_image)
237
- except Exception:
238
- building_analysis = safe_analysis
239
-
240
- # 3) Style name
241
- style_name = ARCH_STYLES[style_choice] if style_choice is not None else ARCH_STYLES[0]
242
-
243
- # 4) Generation (or demo fallback)
244
- if api_working:
245
- generated_image = generate_with_api(style_name)
246
- if generated_image:
247
- status = f"✅ AI Generated: {style_name} | {api_status}"
248
- return building_analysis, status, generated_image
249
- else:
250
- demo_img = create_demo_building(style_name)
251
- status = f"⚠ Generation failed or model loading, showing demo | {api_status}"
252
- return building_analysis, status, demo_img
253
  else:
254
- demo_img = create_demo_building(style_name)
255
- status = f"🎨 Demo Mode: {style_name} | {api_status}"
256
- return building_analysis, status, demo_img
257
-
258
  except Exception as e:
259
- error_img = create_info_image(f"Error: {str(e)}")
260
- status = f" Pipeline error: {str(e)}"
261
- return safe_analysis, status, error_img
262
 
263
- # =========================================================
264
- # Gradio UI
265
- # =========================================================
266
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
267
- gr.Markdown("""
268
- # 🏛 Architectural Style Remix
269
- AI-Powered Building Transformation
270
-
271
- Upload a building → AI analyzes structure → Generates new style
272
- """)
273
 
274
  with gr.Row():
275
  with gr.Column():
276
- input_image = gr.Image(
277
- label="🏢 Upload Building Photo",
278
- type="pil",
279
- height=300
280
- )
281
-
282
- style_dropdown = gr.Dropdown(
283
- choices=ARCH_STYLES,
284
- label="🎨 Select Architectural Style",
285
- value=ARCH_STYLES[2],
286
- type="index"
287
- )
288
-
289
- generate_btn = gr.Button("✨ Generate Style Remix", variant="primary")
290
-
291
- # API test
292
- test_api_btn = gr.Button("🔍 Test API Connection", variant="secondary")
293
- api_status = gr.Textbox(label="API Status", interactive=False)
294
-
295
  with gr.Column():
296
- structure_output = gr.Image(
297
- label="📐 Building Structure Analysis",
298
- height=300
299
- )
300
-
301
- status_text = gr.Textbox(
302
- label="Generation Status",
303
- value="Ready - Upload a building photo to start!",
304
- interactive=False
305
- )
306
-
307
- result_image = gr.Image(
308
- label="🎭 Generated Building",
309
- height=300
310
- )
311
-
312
- gr.Markdown("### 🏗 Try These Building Examples:")
313
- gr.Examples(
314
- examples=[
315
- ["https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=400", 2],
316
- ["https://images.unsplash.com/photo-1580587771525-78b9dba3b914?w=400", 1],
317
- ["https://images.unsplash.com/photo-1513584684374-8bab748fbf90?w=400", 3]
318
- ],
319
- inputs=[input_image, style_dropdown],
320
- outputs=[structure_output, status_text, result_image],
321
- fn=generate_architectural_remix,
322
- cache_examples=False
323
- )
324
-
325
- # Button actions
326
- generate_btn.click(
327
- fn=generate_architectural_remix,
328
- inputs=[input_image, style_dropdown],
329
- outputs=[structure_output, status_text, result_image]
330
- )
331
-
332
- test_api_btn.click(
333
- fn=test_huggingface_api,
334
- outputs=api_status
335
- )
336
-
337
- gr.Markdown("""
338
- ### 🎯 Project Features:
339
- - Structure Analysis: AI detects architectural features
340
- - Style Transformation: AI generates buildings in selected styles
341
- - Demo Mode: Beautiful fallback images when API is unavailable
342
- - API Testing: Check Hugging Face connection status
343
 
344
- Note: Free Hugging Face API has rate limits. Demo mode ensures the project always works!
345
- """)
346
 
347
  if __name__ == "__main__":
348
- demo.launch(debug=True)
 
 
 
 
1
  import os
 
2
  import io
3
+ import gradio as gr
4
+ from PIL import Image, ImageDraw, ImageFont
5
+ from huggingface_hub import InferenceClient
6
+
7
+ # =============== Config ===============
8
+ HF_TOKEN = os.getenv("HF_TOKEN") # set this in your environment
9
+ MODEL_ID = "timbrooks/instruct-pix2pix"
10
+
11
+ STYLES = [
12
+ "Modern Minimalist",
13
+ "Victorian",
14
+ "Brutalist",
15
+ "Mediterranean",
16
+ "Gothic Cathedral",
17
+ "Japanese Zen",
18
+ "Art Deco",
19
+ "Futuristic",
20
+ "Scandinavian",
21
+ "Meditation Retreat"
22
  ]
23
 
24
+ # =============== Helpers ===============
25
+ def info_image(msg: str, w: int = 768, h: int = 512) -> Image.Image:
26
+ """Simple image with centered text (used for errors)."""
27
+ img = Image.new("RGB", (w, h), "#2C3E50")
28
+ d = ImageDraw.Draw(img)
29
+ font = ImageFont.load_default()
30
+ lines = []
31
+ words = msg.split()
32
+ line = ""
33
+ for word in words:
34
+ test = (line + " " + word).strip()
35
+ if d.textlength(test, font=font) < (w - 40):
36
+ line = test
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  else:
38
+ lines.append(line)
39
+ line = word
40
+ if line:
41
+ lines.append(line)
42
+ total_h = len(lines) * 18
43
+ y = (h - total_h) // 2
44
+ for L in lines:
45
+ tw = d.textlength(L, font=font)
46
+ d.text(((w - tw) // 2, y), L, fill="white", font=font)
47
+ y += 18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  return img
49
 
50
 
51
+ def build_prompt(style: str) -> str:
52
+ return (
53
+ f"Transform this house into a {style} architectural style. "
54
+ f"Keep the same perspective and composition. Photorealistic, high detail, realistic lighting."
55
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ # =============== Core ===============
58
+ def transform_house(input_image: Image.Image, style: str):
59
+ """Upload a house image + style -> transformed image from HF model."""
60
+ if input_image is None:
61
+ return info_image("Please upload a house photo first.")
62
 
 
 
63
  if not HF_TOKEN:
64
+ return info_image("HF_TOKEN is missing. Set it in your environment before running.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  try:
67
+ client = InferenceClient(model=MODEL_ID, token=HF_TOKEN)
68
+ prompt = build_prompt(style)
69
+
70
+ # InferenceClient.image_to_image returns bytes in most versions; handle either bytes or PIL.Image
71
+ result = client.image_to_image(
72
+ prompt=prompt,
73
+ image=input_image,
74
+ negative_prompt="blurry, low quality, distorted, text, watermark, extra buildings",
75
+ guidance_scale=7.0,
76
+ image_guidance_scale=1.5,
77
+ num_inference_steps=30,
78
+ )
79
+
80
+ if isinstance(result, bytes):
81
+ return Image.open(io.BytesIO(result)).convert("RGB")
82
+ elif isinstance(result, Image.Image):
83
+ return result.convert("RGB")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  else:
85
+ return info_image("Unexpected response from the model.")
 
 
 
86
  except Exception as e:
87
+ # Graceful error panel instead of crashing the app
88
+ return info_image(f"Generation failed: {e}")
 
89
 
90
+ # =============== UI ===============
91
+ with gr.Blocks() as demo:
92
+ gr.Markdown("# 🏠 House → Style Remix (Hugging Face)")
 
 
 
 
 
 
 
93
 
94
  with gr.Row():
95
  with gr.Column():
96
+ inp = gr.Image(type="pil", label="Upload a house photo")
97
+ style = gr.Dropdown(STYLES, value=STYLES[0], label="Choose a style")
98
+ btn = gr.Button("Generate", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  with gr.Column():
100
+ out = gr.Image(label="Transformed House", height=480)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
+ btn.click(fn=transform_house, inputs=[inp, style], outputs=out)
 
103
 
104
  if __name__ == "__main__":
105
+ # Run: HF_TOKEN=your_token python app.py
106
+ demo.launch(debug=False)