youkii-xr commited on
Commit
4f7b79b
ยท
verified ยท
1 Parent(s): b4abe0e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +275 -339
app.py CHANGED
@@ -1,88 +1,219 @@
1
  import gradio as gr
2
  from ultralytics import YOLO
3
  from PIL import Image
 
4
  import os
 
 
 
5
  from huggingface_hub import hf_hub_download
6
- import numpy as np
7
  import tempfile
8
 
9
- # --- 1. SETUP & MODEL LOADING ---
 
 
 
 
 
10
  MODEL_REPO = "youkii-xr/hieroglyphic-detection"
11
  MODEL_FILENAME = "best.pt"
 
 
 
12
  os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  try:
15
- print("System: Initializing Rosetta Decoder Core...")
16
  model_path = hf_hub_download(
17
  repo_id=MODEL_REPO,
18
  filename=MODEL_FILENAME,
19
- token=os.environ.get("HF_TOKEN")
20
  )
21
  model = YOLO(model_path)
22
  print("System: Model loaded successfully.")
23
  except Exception as e:
24
- print(f"Error: {e}")
25
  model = None
26
 
27
- # --- 2. LOGIC ---
28
- def generate_human_report(detections, counts):
29
- if not detections:
30
- return "Rosetta Decoder detects no identifiable Gardiner codes in this image."
 
 
 
 
 
31
 
32
- total = len(detections)
33
- sorted_counts = sorted(counts.items(), key=lambda item: item[1], reverse=True)
 
 
34
 
35
- report = f"๐Ÿ”“ DECODING SEQUENCE COMPLETE\n"
36
- report += f"===================================\n"
37
- report += f"Glyph Density: {total} Symbols Identified\n\n"
38
- report += "๐Ÿ”ฃ GARDINER CODE INVENTORY:\n"
39
- for code, count in sorted_counts:
40
- report += f"โ€ข Code [{code}]: {count} instance(s)\n"
41
- report += f"\n===================================\n"
42
- report += f"CONFIDENCE: High\n"
43
- report += f"STATUS: Digitized & Ready for Translation."
44
- return report
45
-
46
- def detect_hieroglyphs(image: Image.Image, conf_threshold: float = 0.25):
47
- if image is None:
48
- return None, None, "Input source required.", None
49
- if model is None:
50
- return None, {"error": "Model failed"}, "Critical Error: Weights not loaded.", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  try:
53
  results = model.predict(source=image, conf=conf_threshold, iou=0.45, imgsz=640, verbose=False, device='cpu', max_det=300)
54
  annotated_array = results[0].plot()
55
  annotated_image = Image.fromarray(annotated_array[..., ::-1])
56
-
57
- temp_dir = tempfile.gettempdir()
58
- save_path = os.path.join(temp_dir, "rosetta_decoded_result.jpg")
59
- annotated_image.save(save_path)
60
 
61
  detections = []
62
- gardiner_counts = {}
 
 
63
  for box in results[0].boxes:
64
  if box.cls.numel() > 0:
65
  cls_id = int(box.cls[0])
66
  if 0 <= cls_id < len(model.names):
67
  code = model.names[cls_id]
68
  conf = float(box.conf[0])
69
- if code not in gardiner_counts: gardiner_counts[code] = 0
70
- gardiner_counts[code] += 1
71
- detections.append({"code": code, "confidence": round(conf, 2)})
72
-
73
- summary_json = {
74
- "status": "success",
75
- "total_found": len(detections),
76
- "counts": gardiner_counts
77
- }
 
 
78
 
79
- text_report = generate_human_report(detections, gardiner_counts)
80
- return annotated_image, summary_json, text_report, save_path
 
 
81
 
82
  except Exception as e:
83
- return None, {"error": str(e)}, f"System Failure: {str(e)}", None
84
 
85
- # --- 3. UI STYLING ---
86
 
87
  cursor_url = "url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj4KICA8ZyBmaWxsPSJub25lIiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41Ij4KICAgIDxwYXRoIGQ9Ik0xNiw4IEM2LDIwIDI2LDIwIDE2LDggWiIgZmlsbD0icmdiYSgyMTIsIDE3NSwgNTUsIDAuMSkiLz4KICAgIDxjaXJjbGUgY3g9IjE2IiBjeT0iMTUiIHI9IjMiIGZpbGw9IiNkNGFmMzciLz4KICAgIDxwYXRoIGQ9Ik0xNiwyMiBMMTYsMjggTDEwLDI4Ii8+CiAgPC9nPgo8L3N2Zz4=')"
88
 
@@ -90,7 +221,6 @@ custom_css = f"""
90
  @import url('https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;600;700&display=swap');
91
  @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
92
 
93
- /* --- 1. DEFAULT (DARK/NIGHT MODE) --- */
94
  :root, .dark, body {{
95
  --bg-gradient: radial-gradient(circle at 50% 0%, #0a0a2e 0%, #000000 100%);
96
  --card-bg: rgba(15, 15, 35, 0.7);
@@ -98,14 +228,11 @@ custom_css = f"""
98
  --text-accent: #d4af37;
99
  --border-color: #d4af37;
100
  --btn-grad: linear-gradient(135deg, #b8860b 0%, #d4af37 100%);
101
- --btn-text: #000;
102
  --info-bg: rgba(212, 175, 55, 0.08);
103
  --info-border: #d4af37;
104
  --glow-color: rgba(212, 175, 55, 0.4);
105
- --input-bg: rgba(0,0,0,0.2);
106
  }}
107
 
108
- /* --- 2. LIGHT MODE OVERRIDE (PAPYRUS) --- */
109
  body.light-mode, .gradio-container.light-mode {{
110
  --bg-gradient: linear-gradient(135deg, #f0e6d2 0%, #e6dcc3 100%) !important;
111
  --card-bg: rgba(255, 255, 255, 0.6) !important;
@@ -113,189 +240,49 @@ body.light-mode, .gradio-container.light-mode {{
113
  --text-accent: #8b4513 !important;
114
  --border-color: #8b4513 !important;
115
  --btn-grad: linear-gradient(135deg, #cd853f 0%, #8b4513 100%) !important;
116
- --btn-text: #fff !important;
117
  --info-bg: rgba(139, 69, 19, 0.05) !important;
118
  --info-border: #8b4513 !important;
119
  --glow-color: rgba(139, 69, 19, 0.3) !important;
120
- --input-bg: rgba(255, 255, 255, 0.7) !important;
121
  color: var(--text-primary) !important;
122
  }}
123
 
124
- /* --- GLOBAL LAYOUT --- */
125
  body, .gradio-container {{
126
  background: var(--bg-gradient) !important;
127
  font-family: 'Cairo', sans-serif !important;
128
  color: var(--text-primary) !important;
129
  cursor: {cursor_url} 16 16, auto !important;
130
- -webkit-font-smoothing: antialiased;
131
- transition: background 0.5s ease, color 0.5s ease;
132
- }}
133
-
134
- button, a, .cursor-pointer {{
135
- cursor: {cursor_url} 16 16, pointer !important;
136
- }}
137
-
138
- /* --- TRAIL --- */
139
- .gold-dust {{
140
- position: fixed;
141
- width: 6px;
142
- height: 6px;
143
- background: var(--text-accent);
144
- border-radius: 50%;
145
- pointer-events: none;
146
- z-index: 9999;
147
- animation: fadeDust 0.6s linear forwards;
148
- box-shadow: 0 0 5px var(--text-accent);
149
- }}
150
- @keyframes fadeDust {{
151
- 0% {{ opacity: 1; transform: scale(1); }}
152
- 100% {{ opacity: 0; transform: scale(0); }}
153
- }}
154
-
155
- /* --- CARDS & TITLES --- */
156
- .card {{
157
- background: var(--card-bg) !important;
158
- border: 1px solid rgba(128, 128, 128, 0.2) !important;
159
- border-radius: 12px;
160
- padding: 24px;
161
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
162
- backdrop-filter: blur(12px);
163
- margin-bottom: 24px;
164
- transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
165
- color: var(--text-primary) !important;
166
- }}
167
- .card:hover {{
168
- transform: translateY(-4px);
169
- border-color: var(--border-color) !important;
170
- box-shadow: 0 10px 40px rgba(0,0,0,0.2), 0 0 20px var(--glow-color);
171
- }}
172
- .card-title {{
173
- font-family: 'Cairo', sans-serif;
174
- font-size: 20px;
175
- font-weight: 700;
176
- color: var(--text-accent) !important;
177
- text-transform: uppercase;
178
- letter-spacing: 2px;
179
- border-bottom: 1px solid rgba(128,128,128, 0.2);
180
- padding-bottom: 15px;
181
- margin-bottom: 20px;
182
- display: flex;
183
- align-items: center;
184
- justify-content: center;
185
- gap: 8px;
186
- }}
187
-
188
- /* --- BUTTONS --- */
189
- button.primary-btn {{
190
- background: var(--btn-grad) !important;
191
- border: 1px solid var(--border-color) !important;
192
- color: var(--btn-text) !important;
193
- font-weight: 700 !important;
194
- font-family: 'Cairo', sans-serif !important;
195
- text-transform: uppercase;
196
- letter-spacing: 1px;
197
- transition: all 0.3s ease;
198
- font-size: 16px !important;
199
- }}
200
- button.primary-btn:hover {{
201
- transform: scale(1.02);
202
- box-shadow: 0 0 25px var(--glow-color);
203
- }}
204
- button.toggle-btn {{
205
- background: transparent !important;
206
- border: 1px solid var(--border-color) !important;
207
- color: var(--text-accent) !important;
208
- padding: 5px 15px !important;
209
- font-family: 'Space Mono', monospace;
210
- }}
211
- button.toggle-btn:hover {{
212
- background: var(--info-bg) !important;
213
- }}
214
-
215
- /* --- GUIDE BOXES --- */
216
- .guide-step {{
217
- background-color: var(--info-bg) !important;
218
- border-left: 4px solid var(--info-border) !important;
219
- padding: 16px;
220
- margin: 12px 0;
221
- border-radius: 0 8px 8px 0;
222
- font-family: 'Space Mono', monospace;
223
- font-size: 13px;
224
- line-height: 1.6;
225
- color: var(--text-primary) !important;
226
- }}
227
- .step-number {{
228
- color: var(--text-accent) !important;
229
- font-weight: bold;
230
- text-transform: uppercase;
231
- display: block;
232
- margin-bottom: 6px;
233
- font-size: 12px;
234
- }}
235
- .path-highlight {{
236
- background: rgba(128,128,128,0.2);
237
- padding: 3px 8px;
238
- border-radius: 4px;
239
- color: var(--text-accent) !important;
240
- font-weight: bold;
241
- border: 1px solid var(--border-color);
242
- }}
243
-
244
- /* --- DEEP LIGHT MODE FIXES --- */
245
-
246
- /* 1. Force Image Containers to be Light/Transparent in Light Mode */
247
- body.light-mode .gradio-image,
248
- body.light-mode .image-container,
249
- body.light-mode .gradio-image > div {{
250
- background-color: rgba(255, 255, 255, 0.4) !important;
251
- }}
252
- /* Fix the upload area button specifically */
253
- body.light-mode .gradio-image button {{
254
- background-color: rgba(255, 248, 240, 0.8) !important;
255
- border: 2px dashed var(--border-color) !important;
256
- color: var(--text-primary) !important;
257
- }}
258
-
259
- /* 2. Fix Analysis Log (Textbox) Background & Text */
260
- .report-box textarea {{
261
- background-color: var(--input-bg) !important; /* Uses variable now */
262
- border: 1px solid var(--border-color) !important;
263
- font-family: 'Space Mono', monospace !important;
264
- color: var(--text-primary) !important;
265
- font-size: 14px !important;
266
  }}
267
 
268
- /* 3. Fix Accordion/JSON in Light Mode */
269
- body.light-mode .accordion {{
270
- background-color: rgba(255, 255, 255, 0.5) !important;
271
- border: 1px solid var(--border-color) !important;
272
- }}
273
- body.light-mode .accordion span,
274
- body.light-mode .accordion label {{
275
- color: var(--text-primary) !important;
276
- }}
277
-
278
- /* 4. Fix General Text Visibility */
279
- body.light-mode span, .gradio-container.light-mode span,
280
- body.light-mode p, .gradio-container.light-mode p {{
281
- color: var(--text-primary) !important;
282
- }}
283
- body.light-mode .guide-step code {{
284
- background-color: rgba(139, 69, 19, 0.1);
285
- color: var(--text-accent) !important;
286
- }}
287
- body.light-mode .tab-nav button {{
288
- color: var(--text-primary) !important;
289
- opacity: 0.7;
290
- }}
291
- body.light-mode .tab-nav button.selected {{
292
- color: var(--text-accent) !important;
293
- opacity: 1;
294
- }}
295
-
296
- /* Cleanup */
297
  .gradio-image, .gradio-json {{ background: transparent !important; border: none !important; }}
298
- .block-title {{ color: var(--text-accent) !important; }}
299
  """
300
 
301
  header_html = """
@@ -310,142 +297,91 @@ header_html = """
310
  </svg>
311
  <div>
312
  <h1 style="margin: 0; font-size: 36px; font-weight: 700; color: var(--text-primary); text-shadow: 0 0 10px rgba(212, 175, 55, 0.3);">ROSETTA DECODER</h1>
313
- <p style="margin: 0; font-size: 14px; color: var(--text-accent); letter-spacing: 3px; font-weight: 600;">AI HIEROGLYPHIC TRANSLATION SYSTEM</p>
314
  </div>
315
  </div>
316
  </div>
317
  """
318
 
319
  mission_html = """
320
- <div class="card">
321
- <div class="card-title">๐Ÿ“ก VISION STATEMENT</div>
322
- <p style="opacity: 0.9; font-size: 16px; line-height: 1.8; color: var(--text-primary);">
323
- <b>Bridging the Ancient and the Digital.</b><br>
324
- The Rosetta Decoder project utilizes advanced computer vision to identify and catalog Ancient Egyptian hieroglyphs.
325
- By automating the detection of Gardiner codes, we are creating a digital bridge that will eventually allow for instant,
326
- context-aware translation of Pharaonic wisdom, making the voices of the past accessible to everyone.
327
- </p>
328
- </div>
329
  """
330
 
331
- guide_instructions_html = """
332
- <div class="card" style="border-color: var(--info-border) !important;">
333
- <div class="card-title" style="color: var(--info-border) !important;">๐Ÿค– CLAUDE DESKTOP SETUP GUIDE</div>
334
-
335
- <div class="guide-step">
336
- <span class="step-number">STEP 1: PREPARE WORKSPACE</span>
337
- Claude is sandboxed. It cannot see your Desktop. You must create a bridge.<br>
338
- 1. Create this EXACT folder on your PC: <span class="path-highlight">C:\\Claude_Work</span><br>
339
- 2. Move your hieroglyph images <b>INSIDE</b> this folder.
340
- </div>
341
-
342
- <div class="guide-step">
343
- <span class="step-number">STEP 2: VERIFY PYTHON</span>
344
- The code below assumes Python is at: <code>C:\\Python313\\python.exe</code><br>
345
- <b>Check your path:</b> Open CMD and type <code>where python</code>.<br>
346
- <i>Note: If your path is different, replace the path in the JSON code block below before copying.</i>
347
- </div>
348
 
349
- <div class="guide-step">
350
- <span class="step-number">STEP 3: CONFIGURE CLAUDE</span>
351
- 1. Open Config: <code>%APPDATA%\\Claude\\claude_desktop_config.json</code><br>
352
- 2. Paste the JSON below into the <code>"mcpServers"</code> section.<br>
353
- 3. <b>IMPORTANT:</b> Close Claude from the System Tray (near the clock) and restart it.
354
- </div>
355
 
356
- <div class="guide-step">
357
- <span class="step-number">STEP 4: USAGE</span>
358
- Prompt Claude: <i>"Analyze the image at C:\\Claude_Work\\my_tablet.jpg"</i>
359
- </div>
360
- """
361
 
362
- claude_json_content = """{
363
- "mcpServers": {
364
- "gradio": {
365
- "command": "npx",
366
- "args": [
367
- "mcp-remote",
368
- "https://youkii-xr-hieroglyph-mcp-server.hf.space/gradio_api/mcp/",
369
- "--transport",
370
- "streamable-http"
371
- ]
372
- },
373
- "upload_helper": {
374
- "command": "C:\\\\Python313\\\\python.exe",
375
- "args": [
376
- "-m",
377
- "gradio",
378
- "upload-mcp",
379
- "https://youkii-xr-hieroglyph-mcp-server.hf.space/",
380
- "C:\\\\Claude_Work"
381
- ]
382
- }
383
- }
384
- }"""
385
-
386
- # --- 4. MAIN APP ASSEMBLY ---
387
-
388
- trail_script = """
389
- <script>
390
- document.addEventListener('DOMContentLoaded', () => {
391
- document.addEventListener('mousemove', (e) => {
392
- if (Math.random() > 0.7) return;
393
- const dust = document.createElement('div');
394
- dust.classList.add('gold-dust');
395
- dust.style.left = e.clientX + 'px';
396
- dust.style.top = e.clientY + 'px';
397
- document.body.appendChild(dust);
398
- setTimeout(() => dust.remove(), 600);
399
- });
400
- });
401
- </script>
402
- """
403
 
404
- with gr.Blocks(title="Rosetta Decoder") as demo:
405
  gr.HTML(f"<style>{custom_css}</style>")
406
  gr.HTML(trail_script)
407
 
408
  with gr.Row(elem_classes="header-row"):
409
- with gr.Column(scale=4):
410
- gr.HTML(header_html)
411
- with gr.Column(scale=1):
412
- btn_toggle = gr.Button("๐ŸŒ— Day / Night", elem_classes="toggle-btn")
413
-
414
- gr.HTML(mission_html)
415
-
416
- with gr.Row():
417
- with gr.Column(scale=1):
418
- gr.HTML('<div class="card"><div class="card-title">INPUT IMAGE</div>')
419
- with gr.Tabs():
420
- with gr.TabItem("๐Ÿ“œ Upload File"):
421
- img_upload = gr.Image(type="pil", sources=["upload", "clipboard"], label="Upload", height=320)
422
- slider_upload = gr.Slider(0.1, 1.0, 0.25, label="Scan Sensitivity")
423
- btn_upload = gr.Button("๐Ÿ” START DECODING", elem_classes="primary-btn")
424
-
425
- with gr.TabItem("๐ŸŽฅ Live Camera"):
426
- img_cam = gr.Image(type="pil", sources=["webcam"], label="Camera", height=320)
427
- slider_cam = gr.Slider(0.1, 1.0, 0.25, label="Scan Sensitivity")
428
- btn_cam = gr.Button("๐Ÿ” LIVE DECODE", elem_classes="primary-btn")
429
- gr.HTML('</div>')
430
-
431
- with gr.Column(scale=1):
432
- gr.HTML('<div class="card"><div class="card-title">AI ANALYSIS RESULTS</div>')
433
- out_image = gr.Image(label="Decoded Result", interactive=False)
434
- btn_download = gr.DownloadButton("๐Ÿ’พ DOWNLOAD RESULT", visible=True)
435
- out_report = gr.Textbox(label="Analysis Log", lines=6, elem_classes="report-box", placeholder="Waiting for data stream...")
436
- with gr.Accordion("Raw Glyph Data (JSON)", open=False):
437
- out_json = gr.JSON(label="JSON Data")
438
- gr.HTML('</div>')
439
-
440
- gr.HTML(guide_instructions_html)
441
- gr.Code(value=claude_json_content, language="json", label="claude_desktop_config.json", interactive=False, lines=15)
442
- gr.HTML("</div>")
443
-
444
- # JS Toggle Logic
445
- btn_toggle.click(None, None, None, js="() => { document.body.classList.toggle('light-mode'); document.querySelector('.gradio-container').classList.toggle('light-mode'); }")
446
-
447
- btn_upload.click(fn=detect_hieroglyphs, inputs=[img_upload, slider_upload], outputs=[out_image, out_json, out_report, btn_download])
448
- btn_cam.click(fn=detect_hieroglyphs, inputs=[img_cam, slider_cam], outputs=[out_image, out_json, out_report, btn_download])
 
 
 
 
 
 
 
 
 
 
 
 
 
449
 
450
  if __name__ == "__main__":
451
- demo.launch(mcp_server=True, ssr_mode=False, allowed_paths=["/tmp"])
 
1
  import gradio as gr
2
  from ultralytics import YOLO
3
  from PIL import Image
4
+ from google import genai
5
  import os
6
+ import json
7
+ import matplotlib.pyplot as plt
8
+ import re
9
  from huggingface_hub import hf_hub_download
 
10
  import tempfile
11
 
12
+ # --- 1. CONFIGURATION & SECRETS ---
13
+ # Reading secrets from Hugging Face Environment Variables
14
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
15
+ HF_TOKEN = os.environ.get("HF_TOKEN")
16
+
17
+ # Model Configuration
18
  MODEL_REPO = "youkii-xr/hieroglyphic-detection"
19
  MODEL_FILENAME = "best.pt"
20
+ JSON_DB_PATH = "gardiner_codes.json"
21
+
22
+ # Configure YOLO to use a writable temp directory in HF Spaces
23
  os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
24
 
25
+ # --- 2. DATA LOADING (GARDINER CODES) ---
26
+
27
+ def load_gardiner_database():
28
+ """Loads the Gardiner Code database from the external JSON file."""
29
+ if os.path.exists(JSON_DB_PATH):
30
+ try:
31
+ print(f"System: Loading Gardiner codes from {JSON_DB_PATH}...")
32
+ with open(JSON_DB_PATH, "r", encoding='utf-8') as f:
33
+ return json.load(f)
34
+ except Exception as e:
35
+ print(f"โš ๏ธ Error reading JSON: {e}")
36
+ return {}
37
+ else:
38
+ print(f"โš ๏ธ Warning: {JSON_DB_PATH} not found. Gardiner Code tab will be empty.")
39
+ return {}
40
+
41
+ gardiner_data = load_gardiner_database()
42
+ gardiner_map = {k: v.get("Description", k) for k, v in gardiner_data.items()}
43
+
44
+ # --- 3. HTML TABLE GENERATOR ---
45
+
46
+ CATEGORIES = {
47
+ 'A': "Men & Monarchs", 'B': "Women & Human Activities", 'C': "Deities",
48
+ 'D': "Parts of Human Body", 'E': "Mammals", 'F': "Parts of Mammals",
49
+ 'G': "Birds", 'H': "Parts of Birds", 'I': "Reptiles & Amphibians",
50
+ 'K': "Fishes", 'L': "Invertebrates", 'M': "Trees & Plants",
51
+ 'N': "Sky, Earth, Water", 'O': "Buildings", 'P': "Ships",
52
+ 'Q': "Furniture", 'R': "Temple Furniture", 'S': "Crowns & Dress",
53
+ 'T': "Warfare & Hunting", 'U': "Agriculture & Crafts", 'V': "Rope & Baskets",
54
+ 'W': "Vessels", 'X': "Loaves & Cakes", 'Y': "Writings & Games",
55
+ 'Z': "Strokes & Figures", 'Aa': "Unclassified"
56
+ }
57
+
58
+ def generate_gardiner_html():
59
+ if not gardiner_data:
60
+ return "<tr><td colspan='4'>No data loaded. Please upload gardiner_codes.json to the Space.</td></tr>"
61
+
62
+ html_rows = ""
63
+ grouped = {}
64
+ for key, data in gardiner_data.items():
65
+ match = re.match(r"([A-Za-z]+)", data.get("Code", key))
66
+ prefix = match.group(1) if match else "Unk"
67
+ if prefix not in grouped: grouped[prefix] = []
68
+ grouped[prefix].append(data)
69
+
70
+ sorted_prefixes = sorted(grouped.keys(), key=lambda x: (len(x), x))
71
+
72
+ for prefix in sorted_prefixes:
73
+ cat_name = CATEGORIES.get(prefix, f"Category {prefix}")
74
+ html_rows += f"<tr><td colspan='4' class='category-header'>{cat_name}</td></tr>"
75
+ items = sorted(grouped[prefix], key=lambda x: int(re.search(r'\d+', x.get("Code", "0")).group()) if re.search(r'\d+', x.get("Code", "0")) else 0)
76
+
77
+ for item in items:
78
+ html_rows += f"""
79
+ <tr>
80
+ <td style="font-weight:bold; color: #fff;">{item.get("Code", "?")}</td>
81
+ <td>{item.get("Description", "-")}</td>
82
+ <td style="font-family:serif; font-size:1.1em;">{item.get("Transliteration", "-")}</td>
83
+ <td><span class="type-badge">{item.get("Type", "-")}</span></td>
84
+ </tr>
85
+ """
86
+ return html_rows
87
+
88
+ GARDINER_TABLE_CONTENT = generate_gardiner_html()
89
+
90
+ # --- 4. LOAD MODEL (FROM PRIVATE REPO) ---
91
+
92
+ print("System: Initializing Rosetta Decoder Core...")
93
  try:
 
94
  model_path = hf_hub_download(
95
  repo_id=MODEL_REPO,
96
  filename=MODEL_FILENAME,
97
+ token=HF_TOKEN
98
  )
99
  model = YOLO(model_path)
100
  print("System: Model loaded successfully.")
101
  except Exception as e:
102
+ print(f"Error loading model: {e}")
103
  model = None
104
 
105
+ # --- 5. CORE PROCESSING LOGIC ---
106
+
107
+ def clean_ai_text(text):
108
+ text = re.sub(r'^\d+[\.\)]\s*', '', text)
109
+ text = text.replace("**", "")
110
+ return text
111
+
112
+ def generate_analytics_plots(detections, img_width, img_height):
113
+ if not detections: return None
114
 
115
+ codes = [d['code'] for d in detections]
116
+ confs = [d['confidence'] for d in detections]
117
+ x_centers = [d['box'][0] + (d['box'][2] - d['box'][0])/2 for d in detections]
118
+ y_centers = [d['box'][1] + (d['box'][3] - d['box'][1])/2 for d in detections]
119
 
120
+ fig = plt.figure(figsize=(10, 15))
121
+ fig.patch.set_facecolor('#0f0f23')
122
+
123
+ ax1 = plt.subplot(3, 1, 1)
124
+ unique_codes = list(set(codes))
125
+ counts = [codes.count(c) for c in unique_codes]
126
+ ax1.bar(unique_codes, counts, color='#d4af37')
127
+ ax1.set_title('Symbol Frequency', color='white', fontsize=12, pad=10)
128
+ ax1.tick_params(colors='white')
129
+ ax1.set_facecolor('none')
130
+ for spine in ax1.spines.values(): spine.set_color('#d4af37')
131
+
132
+ ax2 = plt.subplot(3, 1, 2)
133
+ ax2.scatter(range(len(confs)), confs, color='#d4af37', alpha=0.7, s=50)
134
+ ax2.set_title('AI Confidence Levels', color='white', fontsize=12, pad=10)
135
+ ax2.set_ylim(0, 1.1)
136
+ ax2.tick_params(colors='white')
137
+ ax2.set_facecolor('none')
138
+ for spine in ax2.spines.values(): spine.set_color('#d4af37')
139
+
140
+ ax3 = plt.subplot(3, 1, 3)
141
+ h = ax3.hist2d(x_centers, y_centers, bins=[20, 20], range=[[0, img_width], [0, img_height]], cmap='inferno')
142
+ ax3.set_title('Glyph Spatial Heatmap', color='white', fontsize=12, pad=10)
143
+ ax3.set_xlim(0, img_width)
144
+ ax3.set_ylim(img_height, 0)
145
+ ax3.tick_params(colors='white')
146
+ cbar = plt.colorbar(h[3], ax=ax3)
147
+ cbar.ax.yaxis.set_tick_params(color='white')
148
+ plt.setp(plt.getp(cbar.ax.axes, 'yticklabels'), color='white')
149
+
150
+ plt.tight_layout(pad=4.0)
151
+ return fig
152
+
153
+ def get_ai_translation_all_styles(keywords_list):
154
+ if not GOOGLE_API_KEY:
155
+ return "โš ๏ธ Error: Google API Key not found in Secrets.", "Error"
156
+ if not keywords_list:
157
+ return "No hieroglyphs detected.", "No data."
158
+
159
+ keywords_str = ", ".join(keywords_list)
160
+ prompt = f"""
161
+ You are an expert Egyptologist AI. I have detected these symbols: [{keywords_str}].
162
+ Please provide 2 distinct outputs separated by "|||SEPARATOR|||".
163
+ 1. A Mystical Story: Highly atmospheric, sounding like an ancient prophecy.
164
+ Do NOT number this section. Use HTML tags <b> for bolding keywords and <br> for new lines.
165
+ 2. An Academic Translation: Direct, linguistic, focusing on grammar. Use standard text.
166
+ """
167
+ try:
168
+ client = genai.Client(api_key=GOOGLE_API_KEY)
169
+ response = client.models.generate_content(model="gemini-2.5-flash", contents=prompt)
170
+ parts = response.text.split("|||SEPARATOR|||")
171
+ if len(parts) < 2: return clean_ai_text(response.text), "Could not parse academic style."
172
+ return clean_ai_text(parts[0].strip()), clean_ai_text(parts[1].strip())
173
+ except Exception as e:
174
+ return f"Error: {str(e)}", "Error"
175
+
176
+ def process_pipeline(image, conf_threshold):
177
+ if image is None: return None, "", "", None, "", None, []
178
+ if model is None: return None, "Error: Model not loaded.", "", None, "", None, []
179
 
180
  try:
181
  results = model.predict(source=image, conf=conf_threshold, iou=0.45, imgsz=640, verbose=False, device='cpu', max_det=300)
182
  annotated_array = results[0].plot()
183
  annotated_image = Image.fromarray(annotated_array[..., ::-1])
184
+ img_w, img_h = image.size
 
 
 
185
 
186
  detections = []
187
+ unique_codes = set()
188
+ crops = []
189
+
190
  for box in results[0].boxes:
191
  if box.cls.numel() > 0:
192
  cls_id = int(box.cls[0])
193
  if 0 <= cls_id < len(model.names):
194
  code = model.names[cls_id]
195
  conf = float(box.conf[0])
196
+ unique_codes.add(code)
197
+ xyxy = box.xyxy[0].tolist()
198
+ detections.append({"code": code, "confidence": round(conf, 2), "box": xyxy})
199
+
200
+ crop_img = image.crop((xyxy[0], xyxy[1], xyxy[2], xyxy[3]))
201
+ crops.append((crop_img, f"{code}\n({int(conf*100)}%)"))
202
+
203
+ mapped_words = [gardiner_map.get(code, f"[{code}]") for code in unique_codes]
204
+ analytics_plot = generate_analytics_plots(detections, img_w, img_h)
205
+ mystical, academic = get_ai_translation_all_styles(mapped_words)
206
+ text_report = f"Total Symbols: {len(detections)}\nUnique Codes: {', '.join(unique_codes)}"
207
 
208
+ formatted_mystical = f"""<div class="mystical-container"><h3>โœจ THE ANCIENT WHISPER</h3><p>{mystical}</p></div>"""
209
+ json_output = {"count": len(detections), "detections": detections}
210
+
211
+ return annotated_image, formatted_mystical, academic, analytics_plot, text_report, json_output, crops
212
 
213
  except Exception as e:
214
+ return None, f"System Failure: {str(e)}", "", None, str(e), None, []
215
 
216
+ # --- 6. UI STYLING ---
217
 
218
  cursor_url = "url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj4KICA8ZyBmaWxsPSJub25lIiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41Ij4KICAgIDxwYXRoIGQ9Ik0xNiw4IEM2LDIwIDI2LDIwIDE2LDggWiIgZmlsbD0icmdiYSgyMTIsIDE3NSwgNTUsIDAuMSkiLz4KICAgIDxjaXJjbGUgY3g9IjE2IiBjeT0iMTUiIHI9IjMiIGZpbGw9IiNkNGFmMzciLz4KICAgIDxwYXRoIGQ9Ik0xNiwyMiBMMTYsMjggTDEwLDI4Ii8+CiAgPC9nPgo8L3N2Zz4=')"
219
 
 
221
  @import url('https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;600;700&display=swap');
222
  @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
223
 
 
224
  :root, .dark, body {{
225
  --bg-gradient: radial-gradient(circle at 50% 0%, #0a0a2e 0%, #000000 100%);
226
  --card-bg: rgba(15, 15, 35, 0.7);
 
228
  --text-accent: #d4af37;
229
  --border-color: #d4af37;
230
  --btn-grad: linear-gradient(135deg, #b8860b 0%, #d4af37 100%);
 
231
  --info-bg: rgba(212, 175, 55, 0.08);
232
  --info-border: #d4af37;
233
  --glow-color: rgba(212, 175, 55, 0.4);
 
234
  }}
235
 
 
236
  body.light-mode, .gradio-container.light-mode {{
237
  --bg-gradient: linear-gradient(135deg, #f0e6d2 0%, #e6dcc3 100%) !important;
238
  --card-bg: rgba(255, 255, 255, 0.6) !important;
 
240
  --text-accent: #8b4513 !important;
241
  --border-color: #8b4513 !important;
242
  --btn-grad: linear-gradient(135deg, #cd853f 0%, #8b4513 100%) !important;
 
243
  --info-bg: rgba(139, 69, 19, 0.05) !important;
244
  --info-border: #8b4513 !important;
245
  --glow-color: rgba(139, 69, 19, 0.3) !important;
 
246
  color: var(--text-primary) !important;
247
  }}
248
 
 
249
  body, .gradio-container {{
250
  background: var(--bg-gradient) !important;
251
  font-family: 'Cairo', sans-serif !important;
252
  color: var(--text-primary) !important;
253
  cursor: {cursor_url} 16 16, auto !important;
254
+ transition: background 0.5s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  }}
256
 
257
+ .gold-dust {{ position: fixed; width: 6px; height: 6px; background: var(--text-accent); border-radius: 50%; pointer-events: none; z-index: 9999; animation: fadeDust 0.6s linear forwards; box-shadow: 0 0 5px var(--text-accent); }}
258
+ @keyframes fadeDust {{ 0% {{ opacity: 1; transform: scale(1); }} 100% {{ opacity: 0; transform: scale(0); }} }}
259
+
260
+ button, a, .cursor-pointer {{ cursor: {cursor_url} 16 16, pointer !important; }}
261
+ .tabs button {{ padding: 5px 10px !important; font-size: 14px !important; min-width: auto !important; }}
262
+
263
+ .card {{ background: var(--card-bg) !important; border: 1px solid rgba(128, 128, 128, 0.2) !important; border-radius: 12px; padding: 24px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); backdrop-filter: blur(12px); margin-bottom: 24px; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); }}
264
+ .card:hover {{ transform: translateY(-4px); border-color: var(--border-color) !important; box-shadow: 0 10px 40px rgba(0,0,0,0.2), 0 0 20px var(--glow-color); }}
265
+
266
+ .card-title {{ font-family: 'Cairo', sans-serif; font-size: 20px; font-weight: 700; color: var(--text-accent) !important; text-transform: uppercase; letter-spacing: 2px; border-bottom: 1px solid rgba(128,128,128, 0.2); padding-bottom: 15px; margin-bottom: 20px; display: flex; align-items: center; justify-content: center; gap: 8px; }}
267
+ .guide-step {{ background: rgba(255, 255, 255, 0.03); border-left: 4px solid #d4af37; padding: 16px; margin-bottom: 16px; border-radius: 0 6px 6px 0; }}
268
+ .step-title {{ color: #d4af37; font-family: 'Space Mono', monospace; font-weight: bold; display: block; margin-bottom: 8px; font-size: 14px; }}
269
+ .path-highlight {{ background: rgba(212, 175, 55, 0.15); border: 1px solid #d4af37; padding: 2px 6px; border-radius: 4px; color: #fff; font-family: 'Space Mono', monospace; }}
270
+ code {{ font-family: 'Space Mono', monospace; background: rgba(0,0,0,0.3); padding: 2px 5px; border-radius: 4px; color: #e0e7ff; }}
271
+
272
+ .gardiner-table {{ width: 100%; border-collapse: collapse; font-family: 'Space Mono', monospace; font-size: 13px; margin-top: 10px; border: 1px solid #d4af37; }}
273
+ .gardiner-table th {{ color: #ffffff; text-align: left; padding: 12px; border-bottom: 2px solid #d4af37; text-transform: uppercase; letter-spacing: 1px; background: rgba(212, 175, 55, 0.1); }}
274
+ .gardiner-table td {{ padding: 10px; border-bottom: 1px solid rgba(212, 175, 55, 0.2); color: #e0e0e0; }}
275
+ .gardiner-table tr:hover {{ background: rgba(212, 175, 55, 0.1); }}
276
+ .category-header {{ background: rgba(212, 175, 55, 0.2); color: #d4af37; font-weight: bold; text-align: center; padding: 8px; text-transform: uppercase; letter-spacing: 2px; }}
277
+ .type-badge {{ border: 1px solid #d4af37; color: #d4af37; padding: 2px 6px; border-radius: 4px; font-size: 10px; text-transform: uppercase; letter-spacing: 1px; }}
278
+
279
+ .mystical-container {{ font-family: 'Cairo', serif; font-size: 18px; line-height: 1.8; color: #fff8e1; padding: 20px; border: 1px solid var(--border-color); background: rgba(212, 175, 55, 0.05); border-radius: 8px; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); }}
280
+ .mystical-container:hover {{ transform: translateY(-4px); box-shadow: 0 10px 40px rgba(0,0,0,0.2), 0 0 20px var(--glow-color); }}
281
+ .mystical-container h3 {{ color: var(--text-accent); text-align: center; border-bottom: 1px dashed var(--border-color); padding-bottom: 10px; }}
282
+ .mystical-container b {{ color: #d4af37; text-shadow: 0 0 5px rgba(212, 175, 55, 0.5); }}
283
+ .scrollable-box textarea {{ overflow-y: auto !important; max-height: 400px !important; background-color: rgba(0,0,0,0.3) !important; }}
284
+ button.primary-btn {{ background: var(--btn-grad) !important; border: 1px solid var(--border-color) !important; color: #000 !important; font-weight: 700 !important; font-size: 16px !important; }}
 
285
  .gradio-image, .gradio-json {{ background: transparent !important; border: none !important; }}
 
286
  """
287
 
288
  header_html = """
 
297
  </svg>
298
  <div>
299
  <h1 style="margin: 0; font-size: 36px; font-weight: 700; color: var(--text-primary); text-shadow: 0 0 10px rgba(212, 175, 55, 0.3);">ROSETTA DECODER</h1>
300
+ <p style="margin: 0; font-size: 14px; color: var(--text-accent); letter-spacing: 3px; font-weight: 600;">HIEROGLYPHIC DETECTOR AND TRANSLATOR</p>
301
  </div>
302
  </div>
303
  </div>
304
  """
305
 
306
  mission_html = """
307
+ <div class="card"><div class="card-title">๐Ÿ“ก VISION STATEMENT</div><p style="opacity: 0.9; font-size: 16px; line-height: 1.8; color: var(--text-primary);"><b>Bridging the Ancient and the Digital.</b><br>The Rosetta Decoder project utilizes advanced computer vision to identify and catalog Ancient Egyptian hieroglyphs. By automating the detection of Gardiner codes, we are creating a digital bridge that will eventually allow for instant, context-aware translation of Pharaonic wisdom.</p></div>
 
 
 
 
 
 
 
 
308
  """
309
 
310
+ guide_html = """
311
+ <div class="card" style="border-color: #d4af37;">
312
+ <div class="card-title" style="color: #d4af37;">๐Ÿค– CLAUDE DESKTOP SETUP GUIDE</div>
313
+ <div class="guide-step"><span class="step-title">STEP 0: PREREQUISITE</span><p>Ensure you have <b>Node.js</b> installed (Required for the `npx` command used by MCP).</p><p><a href="https://nodejs.org/" target="_blank" style="color: #d4af37; text-decoration: underline;">Download Node.js Official Website</a></p></div>
314
+ <div class="guide-step"><span class="step-title">STEP 1: PREPARE WORKSPACE</span><p>Claude is sandboxed. It cannot see your Desktop. You must create a bridge.</p>1. Create this EXACT folder on your PC: <span class="path-highlight">C:\\Claude_Work</span><br>2. Move your hieroglyph images <b>INSIDE</b> this folder.</div>
315
+ <div class="guide-step"><span class="step-title">STEP 2: VERIFY PYTHON</span><p>The code below assumes Python is at: <code>C:\\Python313\\python.exe</code></p><p><b>Check your path:</b> Open CMD and type <code>where python</code>.</p><p><i>Note: If your path is different, replace the path in the JSON code block below before copying.</i></p></div>
316
+ <div class="guide-step"><span class="step-title">STEP 3: CONFIGURE CLAUDE</span>1. Open Config: <code>%APPDATA%\\Claude\\claude_desktop_config.json</code><br>2. Paste the JSON below into the <code>"mcpServers"</code> section.<br>3. <b>IMPORTANT:</b> Close Claude from the System Tray (near the clock) and restart it.</div>
317
+ <div class="guide-step"><span class="step-title">STEP 4: USAGE</span><p>Prompt Claude: <i>"Analyze the image at C:\\Claude_Work\\my_tablet.jpg"</i></p></div>
318
+ </div>
319
+ """
 
 
 
 
 
 
 
320
 
321
+ claude_json_content = """{ "mcpServers": { "gradio": { "command": "npx", "args": [ "mcp-remote", "https://youkii-xr-hieroglyph-mcp-server.hf.space/gradio_api/mcp/", "--transport", "streamable-http" ] }, "upload_helper": { "command": "C:\\\\Python313\\\\python.exe", "args": [ "-m", "gradio", "upload-mcp", "https://youkii-xr-hieroglyph-mcp-server.hf.space/", "C:\\\\Claude_Work" ] } } }"""
 
 
 
 
 
322
 
323
+ trail_script = """<script>document.addEventListener('DOMContentLoaded', () => { document.addEventListener('mousemove', (e) => { if (Math.random() > 0.7) return; const dust = document.createElement('div'); dust.classList.add('gold-dust'); dust.style.left = e.clientX + 'px'; dust.style.top = e.clientY + 'px'; document.body.appendChild(dust); setTimeout(() => dust.remove(), 600); }); });</script>"""
 
 
 
 
324
 
325
+ # --- 7. MAIN APP ASSEMBLY ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
+ with gr.Blocks(title="Rosetta Decoder Ultimate") as demo:
328
  gr.HTML(f"<style>{custom_css}</style>")
329
  gr.HTML(trail_script)
330
 
331
  with gr.Row(elem_classes="header-row"):
332
+ with gr.Column(scale=4): gr.HTML(header_html)
333
+ with gr.Column(scale=1): btn_toggle = gr.Button("๐ŸŒ— Day / Night")
334
+
335
+ with gr.Tabs():
336
+ # TAB 1: DECODER
337
+ with gr.TabItem("๐Ÿ”ฎ DECODER WORKSTATION"):
338
+ with gr.Row():
339
+ with gr.Column(scale=1):
340
+ gr.HTML('<div class="card"><div class="card-title">Input Source</div>')
341
+ with gr.Tabs():
342
+ with gr.TabItem("๐Ÿ“œ Upload File"):
343
+ img_upload = gr.Image(type="pil", sources=["upload", "clipboard"], label="Upload", height=280)
344
+ slider_conf = gr.Slider(0.1, 1.0, 0.25, label="Scan Sensitivity")
345
+ btn_upload = gr.Button("โœจ START DECODING", elem_classes="primary-btn")
346
+ with gr.TabItem("๐ŸŽฅ Live Camera"):
347
+ img_cam = gr.Image(type="pil", sources=["webcam"], label="Camera", height=280)
348
+ slider_conf_cam = gr.Slider(0.1, 1.0, 0.25, label="Scan Sensitivity")
349
+ btn_cam = gr.Button("โœจ START DECODING", elem_classes="primary-btn")
350
+ gr.HTML('</div>')
351
+
352
+ with gr.Column(scale=1):
353
+ gr.HTML('<div class="card"><div class="card-title">Result</div>')
354
+ with gr.Tabs():
355
+ with gr.TabItem("๐Ÿ”ฎ Mystical"): out_mystical = gr.HTML(label="Prophecy")
356
+ with gr.TabItem("๐Ÿ›๏ธ Academic"): out_academic = gr.Textbox(label="Scientific Translation", lines=15, show_label=False, elem_classes="scrollable-box")
357
+ with gr.TabItem("๐Ÿ–ผ๏ธ Visuals"):
358
+ out_image = gr.Image(label="Annotated Result", interactive=False)
359
+ out_gallery = gr.Gallery(label="Extracted Glyphs", columns=4, height="auto")
360
+ with gr.TabItem("๐Ÿ“Š Analytics"): out_plot = gr.Plot(label="Analysis Charts")
361
+ with gr.TabItem("๐Ÿ› ๏ธ Logs"):
362
+ out_report = gr.Textbox(label="Detection Log", lines=5)
363
+ out_json = gr.JSON(label="JSON Data")
364
+ gr.HTML('</div>')
365
+
366
+ # TAB 2: ABOUT
367
+ with gr.TabItem("๐Ÿ“œ VISION & ABOUT"): gr.HTML(mission_html)
368
+
369
+ # TAB 3: SETUP
370
+ with gr.TabItem("๐Ÿค– SYSTEM SETUP"):
371
+ gr.HTML(guide_html)
372
+ gr.Code(value=claude_json_content, language="json", label="claude_desktop_config.json", interactive=False, lines=15)
373
+
374
+ # TAB 4: GARDINER CODES
375
+ with gr.TabItem("๐“€€ GARDINER CODES"):
376
+ gr.HTML(f"""<div class="card"><div class="card-title">๐Ÿ“œ SUPPORTED GARDINER CODES</div><div style="overflow-x: auto; max-height: 600px; overflow-y: auto;"><table class="gardiner-table"><thead><tr><th>Code</th><th>Description</th><th>Transliteration</th><th>Type</th></tr></thead><tbody>{GARDINER_TABLE_CONTENT}</tbody></table></div></div>""")
377
+
378
+ gr.HTML('<div style="text-align: center; color: var(--text-accent); opacity: 0.5; padding: 20px;"></div>')
379
+
380
+ # Events
381
+ btn_toggle.click(None, None, None, js="() => { document.body.classList.toggle('light-mode'); const container = document.querySelector('.gradio-container'); if(container) container.classList.toggle('light-mode'); }")
382
+ outputs = [out_image, out_mystical, out_academic, out_plot, out_report, out_json, out_gallery]
383
+ btn_upload.click(fn=process_pipeline, inputs=[img_upload, slider_conf], outputs=outputs)
384
+ btn_cam.click(fn=process_pipeline, inputs=[img_cam, slider_conf_cam], outputs=outputs)
385
 
386
  if __name__ == "__main__":
387
+ demo.launch(mcp_server=True, ssr_mode=False, allowed_paths=["/tmp", "."])