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 | |
| # --- 1. SETUP & MODEL LOADING --- | |
| MODEL_REPO = "youkii-xr/hieroglyphic-detection" | |
| MODEL_FILENAME = "best.pt" | |
| os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics" | |
| try: | |
| print("System: Downloading model weights...") | |
| 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 --- | |
| def generate_human_report(detections, counts): | |
| if not detections: | |
| return "The Eye of Horus sees no intelligible glyphs in this image." | |
| total = len(detections) | |
| sorted_counts = sorted(counts.items(), key=lambda item: item[1], reverse=True) | |
| report = f"๐ ANALYSIS COMPLETE\n" | |
| report += f"-----------------------------------\n" | |
| report += f"Total Glyphs Detected: {total}\n\n" | |
| report += "๐ SYMBOL INVENTORY:\n" | |
| for code, count in sorted_counts: | |
| report += f"โข Gardiner Code '{code}': {count} instance(s)\n" | |
| report += f"\n-----------------------------------\n" | |
| report += f"CONFIDENCE ASSESSMENT: High\n" | |
| report += f"TRANSLATION STATUS: Ready for context analysis." | |
| return report | |
| def detect_hieroglyphs(image: Image.Image, conf_threshold: float = 0.25): | |
| if image is None: | |
| return None, None, "Please provide an image input." | |
| if model is None: | |
| return None, {"error": "Model failed"}, "System Error: Model not loaded." | |
| try: | |
| results = model.predict(source=image, conf=conf_threshold, iou=0.45, imgsz=640, verbose=False, device='cpu', max_det=300) | |
| annotated_array = results[0].plot() | |
| annotated_image = Image.fromarray(annotated_array[..., ::-1]) | |
| 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 annotated_image, summary_json, text_report | |
| except Exception as e: | |
| return None, {"error": str(e)}, f"Critical Error: {str(e)}" | |
| # --- 3. THE MYSTICAL UI (CSS) --- | |
| # We use CSS Variables (--name) to handle Light/Dark mode switching automatically. | |
| # Base64 SVG Cursor (Eye of Horus) | |
| cursor_url = "url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj4KICA8ZyBmaWxsPSJub25lIiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMiI+CiAgICA8cGF0aCBkPSZNMTYsOCBDNiwyMCAyNiwyMCAxNiw4IFoiIGZpbGw9InJnYmEoMjEyLCAxNzUsIDU1LqmAuMykiLz4KICAgIDxjaXJjbGUgY3g9IjE2IiBjeT0iMTUiIHI9IjMiIGZpbGw9IiNkNGFmMzciLz4KICAgIDxwYXRoIGQ9Ik0xNiwyMiBMMTYsMjggTDEwLDI4Ii8+CiAgPC9nPgo8L3N2Zz4=')" | |
| 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=Courier+Prime&display=swap'); | |
| /* --- 1. THEME VARIABLES (DAY vs NIGHT) --- */ | |
| :root {{ | |
| /* LIGHT MODE (PAPYRUS DAY) */ | |
| --bg-gradient: linear-gradient(135deg, #fdf6e3 0%, #f5e6d3 100%); | |
| --card-bg: rgba(255, 255, 255, 0.6); | |
| --text-primary: #5c4033; /* Dark Brown */ | |
| --text-accent: #b8860b; /* Dark Gold */ | |
| --border-color: #d4af37; | |
| --highlight-bg: rgba(212, 175, 55, 0.1); | |
| --warning-bg: rgba(255, 0, 0, 0.05); | |
| --warning-border: #cc0000; | |
| }} | |
| .dark {{ | |
| /* DARK MODE (EGYPTIAN NIGHT) */ | |
| --bg-gradient: radial-gradient(circle at 50% 0%, #1a1f35 0%, #050510 100%); | |
| --card-bg: rgba(10, 15, 30, 0.7); | |
| --text-primary: #e0e7ff; /* Light Blue */ | |
| --text-accent: #ffd700; /* Bright Gold */ | |
| --border-color: #d4af37; | |
| --highlight-bg: rgba(212, 175, 55, 0.1); | |
| --warning-bg: rgba(255, 99, 71, 0.1); | |
| --warning-border: #ff6b6b; | |
| }} | |
| /* --- 2. GLOBAL STYLES --- */ | |
| body, .gradio-container {{ | |
| background: var(--bg-gradient) !important; | |
| font-family: 'Cinzel', serif !important; | |
| color: var(--text-primary) !important; | |
| /* MYSTICAL CURSOR */ | |
| cursor: {cursor_url} 16 16, auto !important; | |
| }} | |
| /* BUTTONS & LINKS CURSOR OVERRIDE */ | |
| button, a, .cursor-pointer {{ | |
| cursor: {cursor_url} 16 16, pointer !important; | |
| }} | |
| /* --- 3. ANIMATED CARDS --- */ | |
| .card {{ | |
| background: var(--card-bg) !important; | |
| border: 1px solid rgba(212, 175, 55, 0.3) !important; | |
| border-radius: 15px; | |
| padding: 20px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); | |
| backdrop-filter: blur(10px); | |
| margin-bottom: 20px; | |
| transition: all 0.4s ease; /* Animation Speed */ | |
| }} | |
| /* HOVER ANIMATION: Glow and Lift */ | |
| .card:hover {{ | |
| transform: translateY(-5px); | |
| border-color: var(--border-color) !important; | |
| box-shadow: 0 0 20px rgba(212, 175, 55, 0.4), inset 0 0 10px rgba(212, 175, 55, 0.1); | |
| }} | |
| .card-title {{ | |
| font-size: 16px; | |
| font-weight: 700; | |
| color: var(--text-accent); | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| border-bottom: 1px solid rgba(212, 175, 55, 0.2); | |
| padding-bottom: 8px; | |
| margin-bottom: 15px; | |
| }} | |
| /* --- 4. BUTTONS --- */ | |
| button.primary-btn {{ | |
| background: linear-gradient(135deg, #b8860b 0%, #d4af37 100%) !important; | |
| border: 1px solid #ffd700 !important; | |
| color: #fff !important; | |
| font-weight: bold !important; | |
| font-family: 'Cinzel', serif !important; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| transition: all 0.3s ease; | |
| }} | |
| button.primary-btn:hover {{ | |
| transform: scale(1.02); | |
| box-shadow: 0 0 25px rgba(212, 175, 55, 0.6); | |
| }} | |
| /* --- 5. IMPORTANT NOTES HIGHLIGHTING --- */ | |
| .warning-box {{ | |
| background-color: var(--warning-bg); | |
| border-left: 5px solid var(--warning-border); | |
| padding: 15px; | |
| margin: 10px 0; | |
| border-radius: 0 10px 10px 0; | |
| font-family: 'Courier Prime', monospace; /* Monospace for tech look */ | |
| font-size: 13px; | |
| }} | |
| .highlight-path {{ | |
| background-color: rgba(255, 255, 255, 0.1); | |
| border: 1px dashed var(--border-color); | |
| padding: 2px 6px; | |
| border-radius: 4px; | |
| color: var(--text-accent); | |
| font-weight: bold; | |
| }} | |
| /* REPORT TEXTBOX */ | |
| .report-box textarea {{ | |
| background-color: rgba(0,0,0,0.2) !important; | |
| border: 1px solid var(--border-color) !important; | |
| font-family: 'Courier Prime', monospace !important; | |
| color: var(--text-accent) !important; | |
| }} | |
| /* REMOVE DEFAULT GRADIO BORDERS */ | |
| .gradio-image, .gradio-json {{ background: transparent !important; border: none !important; }} | |
| """ | |
| # HTML HEADER | |
| header_html = """ | |
| <div style="display: flex; align-items: center; gap: 20px; padding: 20px 0;"> | |
| <div style="font-size: 50px;">๐๏ธ</div> | |
| <div> | |
| <h1 style="margin: 0; font-size: 32px; color: var(--text-accent);">HORUS VISION</h1> | |
| <p style="margin: 0; opacity: 0.8; font-size: 12px; letter-spacing: 2px;">HIEROGLYPHIC TRANSLATION & PRESERVATION</p> | |
| </div> | |
| </div> | |
| """ | |
| mission_html = """ | |
| <div class="card"> | |
| <div class="card-title">๐ THE MISSION</div> | |
| <p style="opacity: 0.9; font-size: 14px; line-height: 1.6;"> | |
| <b>To preserve the past is to save the future.</b><br> | |
| This tool uses AI to digitize Ancient Egyptian inscriptions. | |
| <span style="font-size:12px; float:right;">(Hover over cards to see the aura)</span> | |
| </p> | |
| </div> | |
| """ | |
| # NEW DETAILED INSTRUCTIONS HTML | |
| guide_instructions_html = """ | |
| <div class="card" style="border-color: var(--warning-border) !important;"> | |
| <div class="card-title" style="color: var(--warning-border);">๐ค CLAUDE DESKTOP INTEGRATION GUIDE</div> | |
| <div class="warning-box"> | |
| <strong style="font-size:14px; display:block; margin-bottom:5px;">โ ๏ธ CRITICAL: IMAGE PLACEMENT</strong> | |
| Claude Desktop cannot see your entire computer. It is sandboxed.<br><br> | |
| 1. You <b>MUST</b> create this exact folder: <span class="highlight-path">C:\\Claude_Work</span><br> | |
| 2. Place your images <b>INSIDE</b> this folder.<br> | |
| 3. Do <b>NOT</b> drag-and-drop images into Claude. Instead, type: | |
| <i>"Analyze the image at C:\\Claude_Work\\papyrus.jpg"</i> | |
| </div> | |
| <div class="warning-box" style="border-color: var(--text-accent);"> | |
| <strong style="font-size:14px; display:block; margin-bottom:5px;">๐ CRITICAL: CHECK PYTHON PATH</strong> | |
| The config below assumes your Python is at <code>C:\\Python313\\python.exe</code>.<br><br> | |
| If you installed Python normally, it might be in a hidden User folder.<br> | |
| <b>How to check:</b> Open CMD or PowerShell and type <code>where python</code>.<br> | |
| <b>Action:</b> If the path is different, copy the path from CMD and replace it in the JSON below. | |
| </div> | |
| <div style="margin-top: 15px; opacity: 0.9;"> | |
| <b>Step-by-Step Setup:</b><br> | |
| 1. Open your Config file: <code>%APPDATA%\\Claude\\claude_desktop_config.json</code><br> | |
| 2. Paste the code block below.<br> | |
| 3. <b>Fully Quit</b> Claude (Right-click Icon in Tray -> Quit) and restart. | |
| </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 --- | |
| with gr.Blocks(title="Horus Vision", theme=gr.themes.Default()) as demo: | |
| # Inject CSS | |
| gr.HTML(f"<style>{custom_css}</style>") | |
| # Header | |
| gr.HTML(header_html) | |
| gr.HTML(mission_html) | |
| # Workspace | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.HTML('<div class="card"><div class="card-title">SOURCE ARTIFACT</div>') | |
| with gr.Tabs(): | |
| with gr.TabItem("๐ Upload Scroll"): | |
| img_upload = gr.Image(type="pil", sources=["upload", "clipboard"], label="Upload", height=300) | |
| slider_upload = gr.Slider(0.1, 1.0, 0.25, label="Confidence") | |
| btn_upload = gr.Button("๐ฎ DECIPHER SCROLL", elem_classes="primary-btn") | |
| with gr.TabItem("๐๏ธ Divine Sight"): | |
| img_cam = gr.Image(type="pil", sources=["webcam"], label="Camera", height=300) | |
| slider_cam = gr.Slider(0.1, 1.0, 0.25, label="Confidence") | |
| btn_cam = gr.Button("๐ฎ DECIPHER VISION", elem_classes="primary-btn") | |
| gr.HTML('</div>') | |
| with gr.Column(scale=1): | |
| gr.HTML('<div class="card"><div class="card-title">INTERPRETATION</div>') | |
| out_image = gr.Image(label="Annotated Artifact", interactive=False) | |
| out_report = gr.Textbox(label="Scribe's Report", lines=6, elem_classes="report-box", placeholder="Awaiting input...") | |
| with gr.Accordion("Raw Glyph Data (JSON)", open=False): | |
| out_json = gr.JSON(label="JSON Data") | |
| gr.HTML('</div>') | |
| # Config 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>") | |
| # Wiring | |
| btn_upload.click(fn=detect_hieroglyphs, inputs=[img_upload, slider_upload], outputs=[out_image, out_json, out_report]) | |
| btn_cam.click(fn=detect_hieroglyphs, inputs=[img_cam, slider_cam], outputs=[out_image, out_json, out_report]) | |
| if __name__ == "__main__": | |
| demo.launch(mcp_server=True, ssr_mode=False, allowed_paths=["/tmp"]) |