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 (UNCHANGED) --- | |
| MODEL_REPO = "youkii-xr/hieroglyphic-detection" | |
| MODEL_FILENAME = "best.pt" | |
| print(f"Server Status: Public MCP Endpoint Active") | |
| print(f"Security: Model weights are protected (private repo)") | |
| try: | |
| model_path = hf_hub_download( | |
| repo_id=MODEL_REPO, | |
| filename=MODEL_FILENAME, | |
| token=os.environ.get("HF_TOKEN") | |
| ) | |
| print(f"System: Model loaded successfully from private storage.") | |
| model = YOLO(model_path) | |
| except Exception as e: | |
| print(f"CRITICAL ERROR: Could not load model. Check HF_TOKEN in Settings. {e}") | |
| model = None | |
| # --- 2. DETECTION LOGIC (UNCHANGED) --- | |
| def detect_hieroglyphs(image: Image.Image, conf_threshold: float = 0.25): | |
| """ | |
| Analyzes an image to find Egyptian hieroglyphs. | |
| """ | |
| if image is None: | |
| return None, {"error": "No image provided"} | |
| if model is None: | |
| return None, {"error": "Server Error: Model not loaded."} | |
| try: | |
| # Run Inference | |
| results = model.predict( | |
| source=image, | |
| conf=conf_threshold, | |
| iou=0.45, | |
| imgsz=640, | |
| verbose=False, | |
| device='cpu', | |
| max_det=300 | |
| ) | |
| # 1. Generate Visual Output (RGB Image) | |
| annotated_array = results[0].plot() | |
| annotated_image = Image.fromarray(annotated_array[..., ::-1]) | |
| # 2. Generate Data Output (JSON) | |
| 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), | |
| "box": [round(x, 1) for x in box.xyxy[0].cpu().numpy().tolist()] | |
| }) | |
| summary = { | |
| "status": "success", | |
| "total_found": len(detections), | |
| "unique_symbols": list(gardiner_counts.keys()), | |
| "counts": gardiner_counts | |
| } | |
| return annotated_image, summary | |
| except Exception as e: | |
| print(f"Inference Error: {e}") | |
| return None, {"error": str(e)} | |
| # --- 3. UI/UX CONFIGURATION --- | |
| # A. Custom CSS for Animations and Fonts | |
| # Imports 'Cinzel' font for that ancient feel and defines a pulsing gold button | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&display=swap'); | |
| body, .gradio-container { | |
| background-color: #fdf6e3; /* Light Papyrus */ | |
| } | |
| h1, h2, h3 { | |
| font-family: 'Cinzel', serif !important; | |
| color: #8b4513 !important; /* SaddleBrown */ | |
| } | |
| /* The Magic Button Animation */ | |
| @keyframes goldenPulse { | |
| 0% { box-shadow: 0 0 0 0 rgba(212, 175, 55, 0.7); transform: scale(1); } | |
| 50% { box-shadow: 0 0 0 10px rgba(212, 175, 55, 0); transform: scale(1.02); } | |
| 100% { box-shadow: 0 0 0 0 rgba(212, 175, 55, 0); transform: scale(1); } | |
| } | |
| #magic-btn { | |
| background: linear-gradient(135deg, #b8860b 0%, #d4af37 100%); /* Gold Gradient */ | |
| border: 1px solid #8b4513; | |
| color: white; | |
| font-family: 'Cinzel', serif; | |
| font-weight: bold; | |
| font-size: 1.2em; | |
| animation: goldenPulse 2s infinite; | |
| transition: all 0.3s ease; | |
| } | |
| #magic-btn:hover { | |
| animation: none; | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(139, 69, 19, 0.4); | |
| } | |
| .json-output { | |
| background-color: #fff8dc; /* Cornsilk */ | |
| border: 1px solid #d4af37; | |
| } | |
| """ | |
| # B. Custom Theme (Sand, Gold, Lapis) | |
| theme = gr.themes.Soft( | |
| primary_hue="amber", | |
| secondary_hue="slate", | |
| neutral_hue="stone", | |
| ).set( | |
| body_background_fill="#fdf6e3", | |
| block_background_fill="#ffffff", | |
| block_border_color="#d4af37", # Gold borders | |
| button_primary_background_fill="#d4af37", | |
| button_primary_text_color="white", | |
| ) | |
| # C. Claude Configuration JSON Generator | |
| claude_config_content = """ | |
| { | |
| "mcpServers": { | |
| "hieroglyph-detector": { | |
| "command": "uv", | |
| "args": [ | |
| "python", | |
| "client.py" | |
| ], | |
| "env": { | |
| "GRADIO_SERVER_URL": "YOUR_SPACE_URL_HERE" | |
| } | |
| } | |
| } | |
| } | |
| """ | |
| # --- 4. BUILD THE APP WITH BLOCKS --- | |
| with gr.Blocks(theme=theme, css=custom_css, title="Horus Vision") as demo: | |
| # Header Area | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown(""" | |
| # ๐๏ธ Horus Vision | |
| ### AI Hieroglyphic Decoder & Classifier | |
| """) | |
| with gr.Column(scale=3): | |
| gr.Markdown(""" | |
| > *"The eye sees all."* Upload an image of Egyptian text. | |
| > This AI identifies Gardiner codes using the YOLO architecture. | |
| """) | |
| # Main Tabs | |
| with gr.Tabs(): | |
| # TAB 1: The Detector | |
| with gr.TabItem("๐ Decoder"): | |
| with gr.Row(): | |
| # Left Column: Inputs | |
| with gr.Column(): | |
| img_input = gr.Image(type="pil", label="Upload Papyrus/Image", sources=["upload", "clipboard"]) | |
| conf_slider = gr.Slider(minimum=0.1, maximum=1.0, value=0.25, step=0.05, label="Confidence Threshold") | |
| # The Animated Button | |
| analyze_btn = gr.Button("๐ฎ Decipher Symbols", elem_id="magic-btn", variant="primary") | |
| # Right Column: Outputs | |
| with gr.Column(): | |
| img_output = gr.Image(label="Annotated Result", interactive=False) | |
| json_output = gr.JSON(label="Glyph Data", elem_classes="json-output") | |
| # Event Listener | |
| analyze_btn.click( | |
| fn=detect_hieroglyphs, | |
| inputs=[img_input, conf_slider], | |
| outputs=[img_output, json_output] | |
| ) | |
| # TAB 2: Claude Desktop Config | |
| with gr.TabItem("๐ค Connect to Claude"): | |
| gr.Markdown("### MCP Server Configuration") | |
| gr.Markdown("Copy the JSON below into your Claude Desktop config file to use this model directly within Claude.") | |
| gr.Code( | |
| value=claude_config_content, | |
| language="json", | |
| label="claude_desktop_config.json", | |
| interactive=False | |
| ) | |
| # Footer | |
| gr.Markdown("---") | |
| gr.Markdown(f"*Powered by YOLOv8 | Model: {MODEL_REPO} | Private Weights Active*") | |
| # --- 5. LAUNCH --- | |
| if __name__ == "__main__": | |
| demo.launch( | |
| mcp_server=True, | |
| ssr_mode=False, | |
| allowed_paths=["/tmp"] | |
| ) |