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 system detects 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) --- # Eye of Horus Cursor (SVG Base64) cursor_url = "url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj4KICA8ZyBmaWxsPSJub25lIiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMiI+CiAgICA8cGF0aCBkPSZNMTYsOCBDNiwyMCAyNiwyMCAxNiw4IFoiIGZpbGw9InJnYmEoMjEyLCAxNzUsIDU1LCAwLjEpIi8+CiAgICA8cGF0aCBkPSZNMTYsMjIgTDEwLDI4IEwyMiwyOCIvPgogICAgPGNpcmNsZSBjeD0iMTYiIGN5PSIxNSIgcj0iMyIgZmlsbD0iI2Q0YWYzNyIvPgogIDwvZz4KPC9zdmc+')" 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'); /* --- THEME VARIABLES (DAY vs NIGHT) --- */ :root {{ /* DAY MODE (PAPYRUS) */ --bg-gradient: linear-gradient(135deg, #fdf6e3 0%, #f5e6d3 100%); --card-bg: rgba(255, 255, 255, 0.6); --text-primary: #5c4033; --text-accent: #b8860b; --border-color: #d4af37; --warning-bg: rgba(255, 0, 0, 0.05); --warning-border: #cc0000; }} .dark {{ /* NIGHT MODE (LAPIS) */ --bg-gradient: radial-gradient(circle at 50% 0%, #1a1f35 0%, #050510 100%); --card-bg: rgba(10, 15, 30, 0.7); --text-primary: #e0e7ff; --text-accent: #ffd700; --border-color: #d4af37; --warning-bg: rgba(255, 99, 71, 0.1); --warning-border: #ff6b6b; }} /* --- GLOBAL --- */ 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; }} /* --- CARDS & ANIMATIONS --- */ .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; }} /* HOVER EFFECT: Pulse */ .card:hover {{ transform: translateY(-5px); border-color: var(--border-color) !important; box-shadow: 0 0 25px rgba(212, 175, 55, 0.3); }} .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; }} /* --- 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.05); box-shadow: 0 0 30px rgba(212, 175, 55, 0.8); }} /* --- WARNING BOXES --- */ .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; font-size: 13px; }} .path-box {{ background: rgba(128, 128, 128, 0.2); padding: 2px 6px; border-radius: 4px; font-weight: bold; color: var(--text-accent); }} /* 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: 'Courier Prime', monospace !important; color: var(--text-accent) !important; }} """ header_html = """
👁️

HORUS VISION

HIEROGLYPHIC TRANSLATION & PRESERVATION

""" mission_html = """
📜 MISSION BRIEF

To preserve the past is to save the future.
This tool uses AI to digitize Ancient Egyptian inscriptions. (Hover over cards to see the aura)

""" guide_instructions_html = """
🤖 CLAUDE DESKTOP CONFIGURATION
⚠️ STEP 1: IMAGE FOLDER (CRITICAL) Claude Desktop is "sandboxed" (isolated). It cannot see your Desktop or Downloads.

1. Create this EXACT folder: C:\\Claude_Work
2. Put your images INSIDE it.
3. Prompting: "Analyze the image at C:\\Claude_Work\\my_image.jpg"
🐍 STEP 2: PYTHON PATH The code below assumes Python is at C:\\Python313\\python.exe.
If your Python is installed elsewhere, you must change this line in the JSON.
(Run where python in CMD to find your real path).
FINAL STEP: Copy the JSON below into %APPDATA%\\Claude\\claude_desktop_config.json and RESTART Claude.
""" 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 --- # NOTE: removed 'theme' argument to prevent errors. Styling is handled by custom_css. with gr.Blocks(title="Horus Vision") as demo: # Inject CSS gr.HTML(f"") # Header gr.HTML(header_html) gr.HTML(mission_html) # Workspace with gr.Row(): with gr.Column(scale=1): gr.HTML('
SOURCE ARTIFACT
') 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('
') with gr.Column(scale=1): gr.HTML('
INTERPRETATION
') 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('
') # Config Section (Footer) gr.HTML(guide_instructions_html) gr.Code(value=claude_json_content, language="json", label="claude_desktop_config.json", interactive=False, lines=15) gr.HTML("
") # 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"])