Rosetta-Decoder / app.py
youkii-xr's picture
Update app.py
004bb8f verified
Raw
History Blame
12.4 kB
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"])