prateeksharmacoder commited on
Commit
4e2a788
Β·
1 Parent(s): f1f395e

SentiEnhance UI Overhaul

Browse files
Files changed (1) hide show
  1. app.py +263 -341
app.py CHANGED
@@ -63,189 +63,104 @@ def infer_esrgan(image):
63
  # ============================================================
64
 
65
  CSS = """
66
- /* ── Google Font ── */
67
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
68
 
69
  body, .gradio-container {
70
  font-family: 'Inter', sans-serif !important;
 
71
  }
72
 
73
- /* ── Hero header ── */
74
- .hero-header {
75
- background: linear-gradient(135deg, #0f172a 0%, #1e3a5f 50%, #0f172a 100%);
76
- border-radius: 16px;
77
- padding: 36px 40px 28px;
78
- margin-bottom: 8px;
79
- border: 1px solid rgba(56, 189, 248, 0.2);
80
- box-shadow: 0 0 40px rgba(56, 189, 248, 0.08);
81
- position: relative;
82
- overflow: hidden;
83
- }
84
-
85
- .hero-header::before {
86
- content: '';
87
- position: absolute;
88
- top: -60px; right: -60px;
89
- width: 200px; height: 200px;
90
- background: radial-gradient(circle, rgba(56,189,248,0.12) 0%, transparent 70%);
91
- pointer-events: none;
92
- }
93
-
94
- .hero-title {
95
- font-size: 2.1rem;
96
- font-weight: 700;
97
- color: #f0f9ff;
98
- margin: 0 0 8px;
99
- letter-spacing: -0.5px;
100
- }
101
-
102
- .hero-subtitle {
103
- font-size: 0.95rem;
104
- color: #94a3b8;
105
- margin: 0;
106
- line-height: 1.6;
107
- }
108
-
109
- .hero-badge {
110
- display: inline-block;
111
- background: rgba(56, 189, 248, 0.15);
112
- color: #38bdf8;
113
- border: 1px solid rgba(56, 189, 248, 0.3);
114
- border-radius: 20px;
115
- padding: 3px 12px;
116
- font-size: 0.75rem;
117
- font-weight: 600;
118
- letter-spacing: 0.5px;
119
- margin-bottom: 14px;
120
- }
121
-
122
- /* ── Section cards ── */
123
- .card {
124
- background: #1e293b;
125
- border: 1px solid #334155;
126
- border-radius: 12px;
127
- padding: 20px;
128
- margin-bottom: 12px;
129
- }
130
-
131
- .section-label {
132
- font-size: 0.7rem;
133
- font-weight: 600;
134
- letter-spacing: 1.2px;
135
- text-transform: uppercase;
136
- color: #64748b;
137
- margin-bottom: 10px;
138
- }
139
-
140
- /* ── Generate button ── */
141
- #generate-btn {
142
- background: linear-gradient(135deg, #0ea5e9 0%, #2563eb 100%) !important;
143
- border: none !important;
144
- color: white !important;
145
- font-weight: 600 !important;
146
- font-size: 1rem !important;
147
- padding: 14px !important;
148
- border-radius: 10px !important;
149
- letter-spacing: 0.3px;
150
- transition: opacity 0.2s ease, transform 0.1s ease !important;
151
- box-shadow: 0 4px 14px rgba(14, 165, 233, 0.35) !important;
152
- width: 100%;
153
- }
154
- #generate-btn:hover {
155
- opacity: 0.92 !important;
156
- transform: translateY(-1px) !important;
157
- }
158
- #generate-btn:active {
159
- transform: translateY(0px) !important;
160
- }
161
-
162
- /* ── Download button ── */
163
- #download-btn {
164
- background: #0f172a !important;
165
- border: 1px solid #38bdf8 !important;
166
- color: #38bdf8 !important;
167
- font-weight: 600 !important;
168
- border-radius: 8px !important;
169
- padding: 10px !important;
170
- width: 100%;
171
- transition: background 0.2s ease !important;
172
- }
173
- #download-btn:hover {
174
- background: rgba(56,189,248,0.1) !important;
175
- }
176
-
177
- /* ── Status box ── */
178
- .status-box {
179
- background: #0f172a;
180
- border: 1px solid #1e3a5f;
181
- border-radius: 10px;
182
- padding: 16px 20px;
183
- font-size: 0.88rem;
184
- color: #94a3b8;
185
- min-height: 120px;
186
- }
187
-
188
- /* ── Model info chips ── */
189
- .model-chip-hat {
190
- display: inline-block;
191
- background: rgba(16, 185, 129, 0.12);
192
- color: #34d399;
193
- border: 1px solid rgba(16,185,129,0.3);
194
- border-radius: 6px;
195
- padding: 2px 10px;
196
- font-size: 0.72rem;
197
- font-weight: 600;
198
- margin-right: 6px;
199
- }
200
-
201
- .model-chip-esr {
202
- display: inline-block;
203
- background: rgba(168, 85, 247, 0.12);
204
- color: #c084fc;
205
- border: 1px solid rgba(168,85,247,0.3);
206
- border-radius: 6px;
207
- padding: 2px 10px;
208
- font-size: 0.72rem;
209
- font-weight: 600;
210
- }
211
-
212
- /* ── Footer ── */
213
- .footer-note {
214
- background: rgba(249, 115, 22, 0.07);
215
- border: 1px solid rgba(249, 115, 22, 0.25);
216
- border-radius: 8px;
217
- padding: 12px 16px;
218
- font-size: 0.82rem;
219
- color: #fb923c;
220
- margin-top: 4px;
221
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  """
223
 
224
-
225
  # ============================================================
226
  # INFERENCE FUNCTION
227
  # ============================================================
228
 
229
  def run_super_resolution(image_path, model_choice, progress=gr.Progress()):
230
-
231
- # --------------------------------------------------------
232
- # Validate input
233
- # --------------------------------------------------------
234
  if image_path is None:
235
  raise gr.Error("⚠️ Please upload a satellite image before running.")
236
 
237
- # --------------------------------------------------------
238
- # Normalize model name
239
- # --------------------------------------------------------
240
- model_map = {"HATSAT β€” Recommended": "hatsat", "ESRGAN β€” Baseline": "esrgan"}
241
  selected_model = model_map.get(model_choice)
242
-
243
  if selected_model is None:
244
  raise gr.Error("⚠️ Please select a valid model.")
245
 
246
- # --------------------------------------------------------
247
- # Load image (PNG/JPG or GeoTIFF)
248
- # --------------------------------------------------------
249
  if not isinstance(image_path, list):
250
  image_path = [image_path]
251
 
@@ -286,7 +201,7 @@ def run_super_resolution(image_path, model_choice, progress=gr.Progress()):
286
  if data.dtype != np.uint8:
287
  data = normalize_rgb(data)
288
 
289
- image = Image.fromarray(data, mode="RGB")
290
 
291
  elif len(image_path) == 1:
292
  file_path = str(image_path[0])
@@ -321,222 +236,231 @@ def run_super_resolution(image_path, model_choice, progress=gr.Progress()):
321
  else:
322
  data = normalize_rgb(data)
323
 
324
- image = Image.fromarray(data, mode="RGB")
325
  else:
326
  try:
327
- image = Image.open(file_path).convert("RGB")
328
- input_w, input_h = image.size
329
  except Exception:
330
  raise gr.Error("⚠️ Could not read image.")
331
  else:
332
  raise gr.Error("⚠️ Please upload either 1 file (multiband image) or 3 files (B04, B03, B02 GeoTIFFs).")
333
 
334
- # --------------------------------------------------------
335
- # Basic sanity check
336
- # --------------------------------------------------------
337
  if input_w < 16 or input_h < 16:
338
- raise gr.Error(
339
- f"⚠️ Image too small ({input_w}Γ—{input_h}). "
340
- "Please upload an image at least 16Γ—16 pixels."
341
- )
342
-
343
  if input_w > 1024 or input_h > 1024:
344
- raise gr.Error(
345
- f"⚠️ Image too large ({input_w}Γ—{input_h}). "
346
- "For this web demo, please use images up to 1024Γ—1024 pixels to prevent GPU timeouts."
347
- )
348
-
349
- # --------------------------------------------------------
350
- # Run inference
351
- # --------------------------------------------------------
352
- model_label = "HATSAT" if selected_model == "hatsat" else "ESRGAN"
353
-
354
- start_time = time.time()
355
 
 
356
  try:
357
- progress(0.5, desc=f"Running {model_label} 4Γ— super-resolution (ZeroGPU) …")
358
  if selected_model == "hatsat":
359
- result = infer_hatsat(image)
360
  else:
361
- result = infer_esrgan(image)
362
  except Exception as e:
363
- raise gr.Error(f"{model_label} inference failed: {e}")
364
 
365
- elapsed = time.time() - start_time
366
- progress(0.9, desc="Finalising output …")
367
 
368
- output_w, output_h = result.size
 
 
 
 
369
 
370
- # --------------------------------------------------------
371
- # Save to a temp file for download
372
- # --------------------------------------------------------
373
  if is_tiff:
374
- tmp = tempfile.NamedTemporaryFile(
375
- suffix=f"_sr_{selected_model}_4x_georef.tif",
376
- delete=False
377
- )
378
- tmp.close()
379
- save_sr_geotiff(result, profile, tmp.name, scale=4)
380
- out_file_path = tmp.name
381
  else:
382
- tmp = tempfile.NamedTemporaryFile(
383
- suffix=f"_sr_{selected_model}_4x.png",
384
- delete=False
385
- )
386
- result.save(tmp.name, format="PNG")
387
- tmp.close()
388
- out_file_path = tmp.name
389
-
390
- # --------------------------------------------------------
391
- # Info markdown
392
- # --------------------------------------------------------
393
- info = f"""
394
- <div class="status-box">
395
-
396
- **βœ… Super-Resolution Complete**
397
-
398
- | Field | Value |
399
- |---|---|
400
- | **Model** | {model_label} |
401
- | **Scale** | 4Γ— |
402
- | **Input size** | {input_w} Γ— {input_h} px |
403
- | **Output size** | {output_w} Γ— {output_h} px |
404
- | **Processing time** | {elapsed:.2f} s |
405
- | **Nominal output sampling** | ~2.5 m (from 10 m input) |
406
-
407
- </div>
408
- """
409
 
410
  progress(1.0, desc="Done.")
411
- return result, info, out_file_path
 
 
412
 
413
 
414
  # ============================================================
415
- # CLEAR FUNCTION
416
  # ============================================================
417
 
418
- def clear_all():
419
- return None, None, "", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
  # ============================================================
423
- # UI
424
  # ============================================================
425
 
426
- THEME = gr.themes.Base(
427
- primary_hue="sky",
428
- neutral_hue="slate",
429
- )
430
-
431
- with gr.Blocks(
432
- title="Satellite Super-Resolution β€” SIH 26142 (ZeroGPU)",
433
- ) as demo:
434
-
435
- # ── Hero header ─────────────────────────────────────────
436
- gr.HTML("""
437
- <div class="hero-header">
438
- <div class="hero-badge">SIH PROBLEM STATEMENT 26142 Β· NTRO Β· SPACE TECHNOLOGY</div>
439
- <div class="hero-title">πŸ›°οΈ Satellite Image Super-Resolution</div>
440
- <div class="hero-subtitle">
441
- Deep learning–based <strong style="color:#38bdf8">4Γ— super-resolution</strong>
442
- for medium-resolution satellite imagery (Sentinel-2 and similar sources).<br>
443
- Upload an image, select a model, and generate a sharper super-resolved output.
444
- </div>
445
- </div>
446
- """)
447
-
448
- # ── Main layout ─────────────────────────────────────────
449
- with gr.Row(equal_height=False):
450
-
451
- # ── LEFT column: inputs ─────────────────────────────
452
- with gr.Column(scale=5):
453
-
454
- gr.HTML('<div class="section-label">πŸ“‚ Input Image</div>')
455
- input_image = gr.File(
456
- label="Upload Satellite Image (PNG, JPG, TIFF)",
457
- file_count="multiple",
458
- file_types=["image", ".tif", ".tiff"],
459
- elem_id="input-img",
460
- )
461
-
462
- gr.HTML('<div class="section-label" style="margin-top:16px">πŸ€– Model Selection</div>')
463
- model_selector = gr.Radio(
464
- choices=["HATSAT β€” Recommended", "ESRGAN β€” Baseline"],
465
- value="HATSAT β€” Recommended",
466
- label="Super-Resolution Model",
467
- info=(
468
- "HATSAT is fine-tuned for satellite imagery and is the primary model. "
469
- "ESRGAN is a general-purpose baseline included for comparison."
470
- ),
471
- )
472
-
473
  gr.HTML("""
474
- <div class="card" style="margin-top:4px; font-size:0.82rem; color:#94a3b8; line-height:1.7;">
475
- <span class="model-chip-hat">HATSAT</span>
476
- Satellite-oriented Β· HAT transformer Β· 4Γ— upscaling Β· Recommended<br>
477
- <span class="model-chip-esr" style="margin-top:6px; display:inline-block;">ESRGAN</span>
478
- General-purpose Β· RRDB architecture Β· 4Γ— upscaling Β· Baseline comparison
479
- </div>
480
  """)
481
-
482
  with gr.Row():
483
- generate_btn = gr.Button(
484
- "πŸš€ Run Super-Resolution",
485
- variant="primary",
486
- elem_id="generate-btn",
487
- )
488
- clear_btn = gr.Button(
489
- "βœ• Clear",
490
- variant="secondary",
491
- )
492
-
493
- # ── RIGHT column: outputs ────────────────────────────
494
- with gr.Column(scale=5):
495
-
496
- gr.HTML('<div class="section-label">πŸ–ΌοΈ Super-Resolved Output</div>')
497
- output_image = gr.Image(
498
- label="4Γ— Super-Resolved Output",
499
- type="pil",
500
- interactive=False,
501
- elem_id="output-img",
502
- )
503
-
504
- download_file = gr.File(
505
- label="⬇️ Download Result",
506
- interactive=False,
507
- visible=True,
508
- elem_id="download-btn",
509
- )
510
-
511
- gr.HTML('<div class="section-label" style="margin-top:12px">πŸ“Š Processing Info</div>')
512
- output_info = gr.Markdown(
513
- value='<div class="status-box">Results will appear here after processing.</div>',
514
- )
515
-
516
- # ── Disclaimer footer ────────────────────────────────────
517
- gr.HTML("""
518
- <div class="footer-note">
519
- ⚠️ <strong>Important:</strong>
520
- 4Γ— super-resolution produces approximately <strong>2.5 m nominal output sampling</strong>
521
- from a 10 m Sentinel-2 input. This does <em>not</em> mean the system physically recovers
522
- true 2.5 m ground resolution. Results are generated outputs, not real sensor measurements.
523
- </div>
524
- """)
 
 
 
525
 
526
- # ── Events ──────────────────────────────────────────────
527
 
528
- generate_btn.click(
 
 
 
 
529
  fn=run_super_resolution,
530
  inputs=[input_image, model_selector],
531
- outputs=[output_image, output_info, download_file],
532
  show_progress="full",
533
  )
534
 
535
- clear_btn.click(
536
- fn=clear_all,
537
- inputs=[],
538
- outputs=[input_image, output_image, output_info, download_file],
539
- )
540
 
541
 
542
  # ============================================================
@@ -547,7 +471,5 @@ if __name__ == "__main__":
547
  demo.launch(
548
  server_name="0.0.0.0",
549
  server_port=7860,
550
- show_error=True,
551
- theme=THEME,
552
- css=CSS,
553
  )
 
63
  # ============================================================
64
 
65
  CSS = """
66
+ /* Google Font */
67
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
68
 
69
  body, .gradio-container {
70
  font-family: 'Inter', sans-serif !important;
71
+ background-color: #f8fafc !important;
72
  }
73
 
74
+ /* Hide default Gradio padding/footers */
75
+ footer { display: none !important; }
76
+ .gradio-container { max-width: 100% !important; padding: 0 !important; margin: 0 !important; }
77
+
78
+ /* Top Navbar */
79
+ .top-nav {
80
+ background: linear-gradient(135deg, #03182b 0%, #07355c 100%);
81
+ padding: 20px 40px;
82
+ display: flex;
83
+ justify-content: space-between;
84
+ align-items: center;
85
+ color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  }
87
+ .nav-left { display: flex; align-items: center; gap: 16px; }
88
+ .nav-logo-icon { width: 36px; height: 36px; fill: #38bdf8; }
89
+ .nav-title { display: flex; flex-direction: column; }
90
+ .nav-logo-text { font-size: 1.6rem; font-weight: 700; color: #38bdf8; margin: 0; line-height: 1.1; }
91
+ .nav-logo-text span { color: white; }
92
+ .nav-subtitle { font-size: 0.8rem; color: #cbd5e1; margin: 0; }
93
+ .nav-links { display: flex; gap: 30px; font-size: 0.95rem; align-items: center; }
94
+ .nav-links a { color: #cbd5e1; text-decoration: none; font-weight: 500; transition: color 0.2s; }
95
+ .nav-links a:hover { color: white; }
96
+ .nav-links a.active { color: white; border-bottom: 2px solid white; padding-bottom: 4px; }
97
+ .nav-btn { background: #34d399; color: #064e3b !important; padding: 10px 20px; border-radius: 6px; font-weight: 600; text-decoration: none; transition: background 0.2s; }
98
+ .nav-btn:hover { background: #10b981; }
99
+
100
+ /* Sidebar */
101
+ .sidebar-container { background: #f8fafc; padding: 30px 20px; height: 100%; border-right: 1px solid #e2e8f0; display: flex; flex-direction: column; justify-content: space-between; }
102
+ .sidebar-links { display: flex; flex-direction: column; gap: 8px; }
103
+ .sidebar-link { display: flex; align-items: center; padding: 14px 20px; border-radius: 10px; color: #475569; text-decoration: none; font-weight: 500; font-size: 0.95rem; gap: 16px; transition: background 0.2s; cursor: pointer; }
104
+ .sidebar-link:hover { background: #f1f5f9; }
105
+ .sidebar-link.active { background: #d1fae5; color: #065f46; font-weight: 600; }
106
+ .sidebar-link svg { width: 20px; height: 20px; }
107
+ .sidebar-banner { background: #e0f2fe; padding: 24px 20px; border-radius: 16px; text-align: center; color: #0369a1; display: flex; flex-direction: column; align-items: center; gap: 12px; }
108
+ .sidebar-banner svg { width: 40px; height: 40px; color: #0284c7; }
109
+ .sidebar-banner div { font-weight: 600; font-size: 0.95rem; line-height: 1.4; }
110
+
111
+ /* Main Content Area */
112
+ .main-wrapper { padding: 30px 40px; }
113
+ .page-title { font-size: 2.2rem; font-weight: 700; color: #0f172a; margin: 0 0 8px; letter-spacing: -0.5px; }
114
+ .page-title span { color: #0369a1; }
115
+ .page-subtitle { color: #64748b; font-size: 1.05rem; margin: 0 0 30px; }
116
+
117
+ /* Upload box */
118
+ .upload-card { background: white !important; border-radius: 16px !important; padding: 0 !important; border: 1px dashed #cbd5e1 !important; box-shadow: 0 4px 20px rgba(0,0,0,0.03) !important; overflow: hidden; }
119
+
120
+ /* Model Select & Enhance Button */
121
+ .model-card { background: white; border-radius: 12px; padding: 20px; border: 1px solid #e2e8f0; box-shadow: 0 4px 6px rgba(0,0,0,0.02); margin-bottom: 20px; }
122
+ .model-card-title { font-size: 1rem; font-weight: 600; color: #1e293b; margin-bottom: 12px; }
123
+ .model-info-box { background: #f0f9ff; color: #0369a1; padding: 14px; border-radius: 8px; font-size: 0.85rem; margin-top: 12px; display: flex; gap: 12px; align-items: flex-start; line-height: 1.5; }
124
+ #enhance-btn { background: #0f9d58 !important; color: white !important; font-weight: 600 !important; font-size: 1.1rem !important; padding: 16px !important; border-radius: 8px !important; border: none !important; width: 100%; transition: all 0.2s; }
125
+ #enhance-btn:hover { background: #0b8043 !important; transform: translateY(-1px); }
126
+
127
+ /* Features List */
128
+ .features-card { background: #f8fafc; border-radius: 12px; padding: 24px; border: 1px solid #e2e8f0; height: 100%; display: flex; flex-direction: column; justify-content: center; }
129
+ .features-title { font-size: 1.1rem; font-weight: 600; color: #0f172a; margin-bottom: 20px; }
130
+ .feature-item { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; color: #334155; font-size: 0.95rem; font-weight: 500; }
131
+ .feature-icon { width: 24px; height: 24px; color: #0f9d58; flex-shrink: 0; }
132
+
133
+ /* Results */
134
+ .results-header { font-size: 1.4rem; font-weight: 700; color: #0f172a; margin: 40px 0 20px; }
135
+ .image-card { border-radius: 12px !important; overflow: hidden; border: 1px solid #e2e8f0; background: white; padding: 0 !important; box-shadow: 0 4px 12px rgba(0,0,0,0.04); }
136
+
137
+ /* Buttons */
138
+ .download-col { display: flex; flex-direction: column; gap: 12px; padding-top: 36px; }
139
+ .btn-primary { background: #0f9d58 !important; color: white !important; padding: 12px 16px !important; border-radius: 8px !important; font-weight: 600 !important; text-align: center; border: none !important; }
140
+ .btn-outline { background: white !important; color: #334155 !important; border: 1px solid #cbd5e1 !important; padding: 12px 16px !important; border-radius: 8px !important; font-weight: 500 !important; text-align: center; box-shadow: 0 1px 2px rgba(0,0,0,0.02) !important; }
141
+
142
+ /* Footer */
143
+ .footer-strip { background: #03182b; color: white; padding: 24px 40px; display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; margin-top: 40px; }
144
+ .footer-left { display: flex; gap: 10px; align-items: center; color: #cbd5e1; }
145
+ .footer-left strong { color: white; }
146
+ .footer-links { display: flex; gap: 24px; color: #94a3b8; align-items: center; }
147
+ .footer-links a { color: #94a3b8; text-decoration: none; transition: color 0.2s; }
148
+ .footer-links a:hover { color: white; }
149
  """
150
 
 
151
  # ============================================================
152
  # INFERENCE FUNCTION
153
  # ============================================================
154
 
155
  def run_super_resolution(image_path, model_choice, progress=gr.Progress()):
 
 
 
 
156
  if image_path is None:
157
  raise gr.Error("⚠️ Please upload a satellite image before running.")
158
 
159
+ model_map = {"HAT-SAT (Recommended)": "hatsat", "ESRGAN (Baseline)": "esrgan"}
 
 
 
160
  selected_model = model_map.get(model_choice)
 
161
  if selected_model is None:
162
  raise gr.Error("⚠️ Please select a valid model.")
163
 
 
 
 
164
  if not isinstance(image_path, list):
165
  image_path = [image_path]
166
 
 
201
  if data.dtype != np.uint8:
202
  data = normalize_rgb(data)
203
 
204
+ original_image = Image.fromarray(data, mode="RGB")
205
 
206
  elif len(image_path) == 1:
207
  file_path = str(image_path[0])
 
236
  else:
237
  data = normalize_rgb(data)
238
 
239
+ original_image = Image.fromarray(data, mode="RGB")
240
  else:
241
  try:
242
+ original_image = Image.open(file_path).convert("RGB")
243
+ input_w, input_h = original_image.size
244
  except Exception:
245
  raise gr.Error("⚠️ Could not read image.")
246
  else:
247
  raise gr.Error("⚠️ Please upload either 1 file (multiband image) or 3 files (B04, B03, B02 GeoTIFFs).")
248
 
 
 
 
249
  if input_w < 16 or input_h < 16:
250
+ raise gr.Error(f"⚠️ Image too small ({input_w}x{input_h}). Please upload an image at least 16x16 pixels.")
 
 
 
 
251
  if input_w > 1024 or input_h > 1024:
252
+ raise gr.Error(f"⚠️ Image too large ({input_w}x{input_h}). Please use images up to 1024x1024 pixels.")
 
 
 
 
 
 
 
 
 
 
253
 
254
+ start_time = time.time()
255
  try:
256
+ progress(0.5, desc=f"Running {selected_model} 4x super-resolution (ZeroGPU) πŸš€")
257
  if selected_model == "hatsat":
258
+ result = infer_hatsat(original_image)
259
  else:
260
+ result = infer_esrgan(original_image)
261
  except Exception as e:
262
+ raise gr.Error(f"{selected_model} inference failed: {e}")
263
 
264
+ progress(0.9, desc="Finalising output πŸš€")
 
265
 
266
+ # Generate PNG download
267
+ tmp_png = tempfile.NamedTemporaryFile(suffix=f"_sr_{selected_model}_4x.png", delete=False)
268
+ result.save(tmp_png.name, format="PNG")
269
+ tmp_png.close()
270
+ out_png = tmp_png.name
271
 
272
+ # Generate TIFF download if input was TIFF
 
 
273
  if is_tiff:
274
+ tmp_tiff = tempfile.NamedTemporaryFile(suffix=f"_sr_{selected_model}_4x_georef.tif", delete=False)
275
+ tmp_tiff.close()
276
+ save_sr_geotiff(result, profile, tmp_tiff.name, scale=4)
277
+ out_tiff = tmp_tiff.name
 
 
 
278
  else:
279
+ out_tiff = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
 
281
  progress(1.0, desc="Done.")
282
+
283
+ # Return: original preview, enhanced preview, png download, tiff download
284
+ return original_image, result, gr.update(value=out_png, interactive=True), gr.update(value=out_tiff, interactive=True)
285
 
286
 
287
  # ============================================================
288
+ # HTML COMPONENTS
289
  # ============================================================
290
 
291
+ TOP_NAV = """
292
+ <div class="top-nav">
293
+ <div class="nav-left">
294
+ <svg class="nav-logo-icon" viewBox="0 0 24 24"><path d="M12 2L2 22h20L12 2zm0 4.2l6.8 13.8H5.2L12 6.2z"/><path d="M12 10.5l-2.5 5h5z"/></svg>
295
+ <div class="nav-title">
296
+ <h1 class="nav-logo-text">Senti<span>Enhance</span></h1>
297
+ <p class="nav-subtitle">AI-Powered Super-Resolution for Sentinel-2 Imagery</p>
298
+ </div>
299
+ </div>
300
+ <div class="nav-links">
301
+ <a href="#" class="active">Home</a>
302
+ <a href="#">About</a>
303
+ <a href="#">How It Works</a>
304
+ <a href="#">Use Cases</a>
305
+ <a href="#">Contact</a>
306
+ <a href="#" class="nav-btn">Get Started</a>
307
+ </div>
308
+ </div>
309
+ """
310
+
311
+ SIDEBAR = """
312
+ <div class="sidebar-container">
313
+ <div class="sidebar-links">
314
+ <a href="#" class="sidebar-link active">
315
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path></svg>
316
+ Super-Resolution
317
+ </a>
318
+ <a href="#" class="sidebar-link">
319
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>
320
+ Compare
321
+ </a>
322
+ <a href="#" class="sidebar-link">
323
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
324
+ Image Gallery
325
+ </a>
326
+ <a href="#" class="sidebar-link">
327
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
328
+ Documentation
329
+ </a>
330
+ <a href="#" class="sidebar-link">
331
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
332
+ About Project
333
+ </a>
334
+ </div>
335
+ <div class="sidebar-banner">
336
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
337
+ <div>Satellite data<br>for a sustainable<br>future</div>
338
+ </div>
339
+ </div>
340
+ """
341
+
342
+ WHY_USE_US = """
343
+ <div class="features-title">Why Use Our Platform?</div>
344
+ <div class="feature-item">
345
+ <svg class="feature-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
346
+ AI-powered super-resolution
347
+ </div>
348
+ <div class="feature-item">
349
+ <svg class="feature-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"></path></svg>
350
+ Supports Sentinel-2 imagery
351
+ </div>
352
+ <div class="feature-item">
353
+ <svg class="feature-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
354
+ Preserves geospatial metadata
355
+ </div>
356
+ <div class="feature-item">
357
+ <svg class="feature-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg>
358
+ Easy to use web interface
359
+ </div>
360
+ <div class="feature-item">
361
+ <svg class="feature-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
362
+ Free for research & education
363
+ </div>
364
+ """
365
 
366
+ FOOTER = """
367
+ <div class="footer-strip">
368
+ <div class="footer-left">
369
+ <strong>SentiEnhance</strong> | AI for a Sustainable Planet
370
+ </div>
371
+ <div class="footer-links">
372
+ <a href="#">Privacy Policy</a> |
373
+ <a href="#">Terms of Use</a> |
374
+ <a href="#">Contact</a>
375
+ <a href="#"><svg width="18" height="18" fill="currentColor" viewBox="0 0 24 24"><path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/></svg></a>
376
+ <a href="#"><svg width="18" height="18" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg></a>
377
+ </div>
378
+ </div>
379
+ """
380
 
381
  # ============================================================
382
+ # APP LAYOUT
383
  # ============================================================
384
 
385
+ with gr.Blocks(theme=gr.themes.Base(), css=CSS) as demo:
386
+
387
+ gr.HTML(TOP_NAV)
388
+
389
+ with gr.Row():
390
+ with gr.Column(scale=1, min_width=240, elem_classes="sidebar"):
391
+ gr.HTML(SIDEBAR)
392
+
393
+ with gr.Column(scale=5, elem_classes="main-wrapper"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  gr.HTML("""
395
+ <h2 class="page-title">Enhance <span>Sentinel-2</span> Satellite Images</h2>
396
+ <p class="page-subtitle">Upload your Sentinel-2 image (GeoTIFF, TIFF, JP2, or PNG) and use AI to generate a high-resolution version.</p>
 
 
 
 
397
  """)
398
+
399
  with gr.Row():
400
+ with gr.Column(scale=4, elem_classes="upload-card"):
401
+ input_image = gr.File(
402
+ label="Drag & drop your satellite image here",
403
+ file_count="multiple",
404
+ file_types=["image", ".tif", ".tiff", ".jp2"]
405
+ )
406
+
407
+ with gr.Column(scale=3):
408
+ with gr.Group(elem_classes="model-card"):
409
+ gr.HTML("<div class='model-card-title'>Model Selection</div>")
410
+ model_selector = gr.Dropdown(
411
+ choices=["HAT-SAT (Recommended)", "ESRGAN (Baseline)"],
412
+ value="HAT-SAT (Recommended)",
413
+ show_label=False,
414
+ container=False
415
+ )
416
+ gr.HTML("""
417
+ <div class="model-info-box">
418
+ <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>
419
+ <span><strong>HAT-SAT:</strong> State-of-the-art model for satellite image super-resolution with high detail and spectral consistency.</span>
420
+ </div>
421
+ """)
422
+ enhance_btn = gr.Button("✨ Enhance Image", elem_id="enhance-btn")
423
+ gr.HTML("<div style='text-align:center; font-size:0.85rem; color:#94a3b8; margin-top:8px;'>Processing may take a few moments...</div>")
424
+
425
+ with gr.Column(scale=3, elem_classes="features-card"):
426
+ gr.HTML(WHY_USE_US)
427
+
428
+ # Results Section
429
+ gr.HTML("<div class='results-header'>Results</div>")
430
+
431
+ with gr.Row():
432
+ with gr.Column(scale=4, elem_classes="image-card"):
433
+ original_image = gr.Image(label="Original (Sentinel-2, 10m)", type="pil", interactive=False)
434
+
435
+ with gr.Column(scale=4, elem_classes="image-card"):
436
+ enhanced_image = gr.Image(label="Enhanced (HAT-SAT, 2x)", type="pil", interactive=False)
437
+
438
+ with gr.Column(scale=3, elem_classes="download-col"):
439
+ gr.HTML("<div style='font-weight:700; font-size:1.05rem; color:#0f172a; margin-bottom:4px;'>Download & Share</div>")
440
+
441
+ download_png = gr.DownloadButton("πŸ“₯ Download Enhanced Image", elem_classes="btn-primary")
442
+ download_tiff = gr.DownloadButton("πŸ“„ Download as GeoTIFF", elem_classes="btn-outline")
443
+ compare_btn = gr.Button("πŸ” Compare Images", elem_classes="btn-outline")
444
+ share_btn = gr.Button("πŸ”— Share Result", elem_classes="btn-outline")
445
 
446
+ gr.HTML(FOOTER)
447
 
448
+ # ============================================================
449
+ # EVENTS
450
+ # ============================================================
451
+
452
+ enhance_btn.click(
453
  fn=run_super_resolution,
454
  inputs=[input_image, model_selector],
455
+ outputs=[original_image, enhanced_image, download_png, download_tiff],
456
  show_progress="full",
457
  )
458
 
459
+ # Interactive elements that don't do anything functionally but can show alerts
460
+ def show_alert():
461
+ gr.Info("This feature is coming soon!")
462
+ compare_btn.click(fn=show_alert)
463
+ share_btn.click(fn=show_alert)
464
 
465
 
466
  # ============================================================
 
471
  demo.launch(
472
  server_name="0.0.0.0",
473
  server_port=7860,
474
+ show_error=True
 
 
475
  )