Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| from ultralytics import YOLO | |
| from PIL import Image | |
| import os | |
| from huggingface_hub import hf_hub_download | |
| import numpy as np | |
| import tempfile | |
| # --- 1. SETUP & MODEL LOADING --- | |
| MODEL_REPO = "youkii-xr/hieroglyphic-detection" | |
| MODEL_FILENAME = "best.pt" | |
| # Fix for the Ultralytics config warning in containers | |
| os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics" | |
| try: | |
| print("System: Initializing Rosetta Decoder Core...") | |
| model_path = hf_hub_download( | |
| repo_id=MODEL_REPO, | |
| filename=MODEL_FILENAME, | |
| token=os.environ.get("HF_TOKEN") | |
| ) | |
| model = YOLO(model_path) | |
| print("System: Model loaded successfully.") | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| model = None | |
| # --- 2. LOGIC: DECODING & REPORTING --- | |
| def generate_human_report(detections, counts): | |
| """Generates a natural language summary of the findings.""" | |
| if not detections: | |
| return "Rosetta Decoder detects no identifiable Gardiner codes in this image." | |
| total = len(detections) | |
| sorted_counts = sorted(counts.items(), key=lambda item: item[1], reverse=True) | |
| report = f"π DECODING SEQUENCE COMPLETE\n" | |
| report += f"===================================\n" | |
| report += f"Glyph Density: {total} Symbols Identified\n\n" | |
| report += "π£ GARDINER CODE INVENTORY:\n" | |
| for code, count in sorted_counts: | |
| report += f"β’ Code [{code}]: {count} instance(s)\n" | |
| report += f"\n===================================\n" | |
| report += f"CONFIDENCE: High\n" | |
| report += f"STATUS: Digitized & Ready for Translation." | |
| return report | |
| def detect_hieroglyphs(image: Image.Image, conf_threshold: float = 0.25): | |
| if image is None: | |
| return None, None, "Input source required.", None | |
| if model is None: | |
| return None, {"error": "Model failed"}, "Critical Error: Weights not loaded.", None | |
| try: | |
| results = model.predict(source=image, conf=conf_threshold, iou=0.45, imgsz=640, verbose=False, device='cpu', max_det=300) | |
| # Visual | |
| annotated_array = results[0].plot() | |
| annotated_image = Image.fromarray(annotated_array[..., ::-1]) | |
| # Save for Download Button | |
| # We create a temp file so the download button can find it | |
| temp_dir = tempfile.gettempdir() | |
| save_path = os.path.join(temp_dir, "rosetta_decoded_result.jpg") | |
| annotated_image.save(save_path) | |
| # Data | |
| detections = [] | |
| gardiner_counts = {} | |
| for box in results[0].boxes: | |
| if box.cls.numel() > 0: | |
| cls_id = int(box.cls[0]) | |
| if 0 <= cls_id < len(model.names): | |
| code = model.names[cls_id] | |
| conf = float(box.conf[0]) | |
| if code not in gardiner_counts: gardiner_counts[code] = 0 | |
| gardiner_counts[code] += 1 | |
| detections.append({"code": code, "confidence": round(conf, 2)}) | |
| summary_json = { | |
| "status": "success", | |
| "total_found": len(detections), | |
| "counts": gardiner_counts | |
| } | |
| text_report = generate_human_report(detections, gardiner_counts) | |
| # Return: Image, JSON, Text, FilePath (for download) | |
| return annotated_image, summary_json, text_report, save_path | |
| except Exception as e: | |
| return None, {"error": str(e)}, f"System Failure: {str(e)}", None | |
| # --- 3. UI STYLING (HTML/CSS INJECTION) --- | |
| # SVG Cursor: A stylized "Lens" | |
| cursor_url = "url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj4KICA8ZyBmaWxsPSJub25lIiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41Ij4KICAgIDxjaXJjbGUgY3g9IjE2IiBjeT0iMTYiIHI9IjgiIGZpbGw9InJnYmEoMjEyLCAxNzUsIDU1LCAwLjEpIi8+CiAgICA8cGF0aCBkPSZNMTYsNCBMMTYsOCIvPgogICAgPHBhdGggZD0iTTE2LDI0IEwxNiwyOCIvPgogICAgPHBhdGggZD0iTTQsMTYgTDgsMTYiLz4KICAgIDxwYXRoIGQ9Ik0yNCwxNiBMMjgsMTYiLz4KICAgIDxjaXJjbGUgY3g9IjE2IiBjeT0iMTYiIHI9IjIiIGZpbGw9IiNkNGFmMzciLz4KICA8L2c+Cjwvc3ZnPg==')" | |
| custom_css = f""" | |
| @import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&display=swap'); | |
| @import url('https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap'); | |
| /* --- THEME VARIABLES --- */ | |
| :root {{ | |
| /* DAY MODE */ | |
| --bg-gradient: linear-gradient(135deg, #f0e6d2 0%, #e6dcc3 100%); | |
| --card-bg: rgba(255, 255, 255, 0.6); | |
| --text-primary: #3d342b; | |
| --text-accent: #8b4513; /* Saddle Brown */ | |
| --border-color: #8b4513; | |
| --btn-grad: linear-gradient(135deg, #cd853f 0%, #8b4513 100%); | |
| --btn-text: #fff; | |
| --glow-color: rgba(139, 69, 19, 0.3); | |
| /* Guide Box Colors (Golden/Info) */ | |
| --info-bg: rgba(212, 175, 55, 0.1); | |
| --info-border: #d4af37; | |
| }} | |
| .dark {{ | |
| /* DARK MODE */ | |
| --bg-gradient: radial-gradient(circle at 50% 0%, #1c1c1c 0%, #0a0a0a 100%); | |
| --card-bg: rgba(30, 30, 30, 0.7); | |
| --text-primary: #dcdcdc; | |
| --text-accent: #d4af37; /* Gold */ | |
| --border-color: #d4af37; | |
| --btn-grad: linear-gradient(135deg, #b8860b 0%, #d4af37 100%); | |
| --btn-text: #000; | |
| --glow-color: rgba(212, 175, 55, 0.4); | |
| /* Guide Box Colors (Golden/Info) */ | |
| --info-bg: rgba(212, 175, 55, 0.1); | |
| --info-border: #d4af37; | |
| }} | |
| /* --- GLOBAL SETTINGS --- */ | |
| body, .gradio-container {{ | |
| background: var(--bg-gradient) !important; | |
| font-family: 'Cinzel', serif !important; | |
| color: var(--text-primary) !important; | |
| cursor: {cursor_url} 16 16, auto !important; | |
| }} | |
| button, a, .cursor-pointer {{ | |
| cursor: {cursor_url} 16 16, pointer !important; | |
| }} | |
| /* --- ANIMATED CARDS --- */ | |
| .card {{ | |
| background: var(--card-bg) !important; | |
| border: 1px solid rgba(128, 128, 128, 0.2) !important; | |
| border-radius: 12px; | |
| padding: 24px; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); | |
| backdrop-filter: blur(12px); | |
| margin-bottom: 24px; | |
| transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); | |
| }} | |
| .card:hover {{ | |
| transform: translateY(-4px); | |
| border-color: var(--border-color) !important; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.3), 0 0 15px var(--glow-color); | |
| }} | |
| /* --- BUTTONS --- */ | |
| button.primary-btn {{ | |
| background: var(--btn-grad) !important; | |
| border: 1px solid var(--border-color) !important; | |
| color: var(--btn-text) !important; | |
| font-weight: 700 !important; | |
| font-family: 'Space Mono', monospace !important; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| transition: all 0.3s ease; | |
| }} | |
| button.primary-btn:hover {{ | |
| transform: scale(1.02); | |
| box-shadow: 0 0 20px var(--glow-color); | |
| }} | |
| /* --- TYPOGRAPHY --- */ | |
| .card-title {{ | |
| font-family: 'Space Mono', monospace; | |
| font-size: 14px; | |
| font-weight: 700; | |
| color: var(--text-accent); | |
| text-transform: uppercase; | |
| letter-spacing: 3px; | |
| border-bottom: 1px solid rgba(128,128,128, 0.2); | |
| padding-bottom: 12px; | |
| margin-bottom: 18px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| }} | |
| /* --- GOLDEN GUIDE BOXES --- */ | |
| .guide-step {{ | |
| background-color: var(--info-bg); | |
| border-left: 4px solid var(--info-border); | |
| padding: 16px; | |
| margin: 12px 0; | |
| border-radius: 0 8px 8px 0; | |
| font-family: 'Space Mono', monospace; | |
| font-size: 13px; | |
| line-height: 1.6; | |
| }} | |
| .step-number {{ | |
| color: var(--text-accent); | |
| font-weight: bold; | |
| text-transform: uppercase; | |
| display: block; | |
| margin-bottom: 6px; | |
| font-size: 11px; | |
| letter-spacing: 1px; | |
| }} | |
| .path-highlight {{ | |
| background: rgba(128,128,128,0.2); | |
| padding: 2px 6px; | |
| border-radius: 4px; | |
| color: var(--text-accent); | |
| font-weight: bold; | |
| }} | |
| /* UI CLEANUP */ | |
| .gradio-image, .gradio-json {{ background: transparent !important; border: none !important; }} | |
| .report-box textarea {{ | |
| background-color: rgba(0,0,0,0.2) !important; | |
| border: 1px solid var(--border-color) !important; | |
| font-family: 'Space Mono', monospace !important; | |
| color: var(--text-accent) !important; | |
| font-size: 13px !important; | |
| }} | |
| """ | |
| header_html = """ | |
| <div style="padding: 20px 0; border-bottom: 1px solid rgba(128,128,128,0.1); margin-bottom: 20px;"> | |
| <div style="display: flex; align-items: center; gap: 15px;"> | |
| <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="var(--text-accent)" stroke-width="2"> | |
| <rect x="3" y="3" width="18" height="18" rx="2" /> | |
| <path d="M7 7h10" /> | |
| <path d="M7 12h10" /> | |
| <path d="M7 17h10" /> | |
| <circle cx="12" cy="12" r="3" stroke="var(--text-accent)" fill="none"/> | |
| </svg> | |
| <div> | |
| <h1 style="margin: 0; font-size: 28px; font-family: 'Cinzel', serif; letter-spacing: 1px; color: var(--text-primary);">ROSETTA DECODER</h1> | |
| <p style="margin: 0; font-family: 'Space Mono', monospace; font-size: 11px; color: var(--text-accent); letter-spacing: 3px;">AI HIEROGLYPHIC TRANSLATION SYSTEM</p> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| mission_html = """ | |
| <div class="card"> | |
| <div class="card-title">π‘ VISION STATEMENT</div> | |
| <p style="opacity: 0.9; font-size: 15px; line-height: 1.8;"> | |
| <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, making the voices of the past accessible to everyone. | |
| </p> | |
| </div> | |
| """ | |
| guide_instructions_html = """ | |
| <div class="card" style="border-color: var(--info-border) !important;"> | |
| <div class="card-title" style="color: var(--info-border) !important;">π€ CLAUDE DESKTOP SETUP GUIDE</div> | |
| <div class="guide-step"> | |
| <span class="step-number">STEP 1: PREPARE WORKSPACE</span> | |
| Claude is sandboxed. It cannot see your Desktop. You must create a bridge.<br> | |
| 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> | |
| <div class="guide-step"> | |
| <span class="step-number">STEP 2: VERIFY PYTHON</span> | |
| The code below assumes Python is at: <code>C:\\Python313\\python.exe</code><br> | |
| <b>Check your path:</b> Open CMD and type <code>where python</code>.<br> | |
| <i>Note: If your path is different, replace the path in the JSON code block below before copying.</i> | |
| </div> | |
| <div class="guide-step"> | |
| <span class="step-number">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> | |
| <div class="guide-step"> | |
| <span class="step-number">STEP 4: USAGE</span> | |
| Prompt Claude: <i>"Analyze the image at C:\\Claude_Work\\my_tablet.jpg"</i> | |
| </div> | |
| """ | |
| 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" | |
| ] | |
| } | |
| } | |
| }""" | |
| # --- 4. MAIN APP ASSEMBLY --- | |
| with gr.Blocks(title="Rosetta Decoder") as demo: | |
| # Inject Styles | |
| gr.HTML(f"<style>{custom_css}</style>") | |
| # Top Section | |
| gr.HTML(header_html) | |
| gr.HTML(mission_html) | |
| # Workspace | |
| with gr.Row(): | |
| # INPUT COLUMN | |
| with gr.Column(scale=1): | |
| gr.HTML('<div class="card"><div class="card-title">INPUT IMAGE</div>') | |
| with gr.Tabs(): | |
| with gr.TabItem("π Upload File"): | |
| img_upload = gr.Image(type="pil", sources=["upload", "clipboard"], label="Upload", height=320) | |
| slider_upload = gr.Slider(0.1, 1.0, 0.25, label="Scan Sensitivity") | |
| btn_upload = gr.Button("π START DECODING", elem_classes="primary-btn") | |
| with gr.TabItem("π₯ Live Camera"): | |
| img_cam = gr.Image(type="pil", sources=["webcam"], label="Camera", height=320) | |
| slider_cam = gr.Slider(0.1, 1.0, 0.25, label="Scan Sensitivity") | |
| btn_cam = gr.Button("π LIVE DECODE", elem_classes="primary-btn") | |
| gr.HTML('</div>') | |
| # OUTPUT COLUMN | |
| with gr.Column(scale=1): | |
| gr.HTML('<div class="card"><div class="card-title">AI ANALYSIS RESULTS</div>') | |
| out_image = gr.Image(label="Decoded Result", interactive=False) | |
| # Download Button (Standard Gradio Component) | |
| btn_download = gr.DownloadButton("πΎ DOWNLOAD RESULT", visible=True) | |
| out_report = gr.Textbox(label="Analysis Log", lines=6, elem_classes="report-box", placeholder="Waiting for data stream...") | |
| with gr.Accordion("Raw Glyph Data (JSON)", open=False): | |
| out_json = gr.JSON(label="JSON Data") | |
| gr.HTML('</div>') | |
| # Footer Section | |
| gr.HTML(guide_instructions_html) | |
| gr.Code(value=claude_json_content, language="json", label="claude_desktop_config.json", interactive=False, lines=15) | |
| gr.HTML("</div>") | |
| # Event Wiring | |
| # Note: detect_hieroglyphs now returns 4 values: Image, JSON, Text, FilePath | |
| btn_upload.click( | |
| fn=detect_hieroglyphs, | |
| inputs=[img_upload, slider_upload], | |
| outputs=[out_image, out_json, out_report, btn_download] | |
| ) | |
| btn_cam.click( | |
| fn=detect_hieroglyphs, | |
| inputs=[img_cam, slider_cam], | |
| outputs=[out_image, out_json, out_report, btn_download] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(mcp_server=True, ssr_mode=False, allowed_paths=["/tmp"]) |