Rosetta-Decoder / app.py
youkii-xr's picture
Update app.py
de27937 verified
Raw
History Blame
11.6 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 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 = """
<div style="display: flex; align-items: center; gap: 20px; padding: 20px 0;">
<div style="font-size: 40px;">๐Ÿ‘๏ธ</div>
<div>
<h1 style="margin: 0; font-size: 32px; color: var(--text-accent);">HORUS VISION</h1>
<p style="margin: 0; font-size: 12px; letter-spacing: 2px; opacity: 0.8;">HIEROGLYPHIC TRANSLATION & PRESERVATION</p>
</div>
</div>
"""
mission_html = """
<div class="card">
<div class="card-title">๐Ÿ“œ MISSION BRIEF</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>
"""
guide_instructions_html = """
<div class="card" style="border-color: var(--warning-border) !important;">
<div class="card-title" style="color: var(--warning-border);">๐Ÿค– CLAUDE DESKTOP CONFIGURATION</div>
<div class="warning-box">
<strong style="font-size:14px; display:block; margin-bottom:5px; color: var(--warning-border);">โš ๏ธ STEP 1: IMAGE FOLDER (CRITICAL)</strong>
Claude Desktop is "sandboxed" (isolated). It cannot see your Desktop or Downloads.<br><br>
1. Create this EXACT folder: <span class="path-box">C:\\Claude_Work</span><br>
2. Put your images INSIDE it.<br>
3. <b>Prompting:</b> "Analyze the image at C:\\Claude_Work\\my_image.jpg"
</div>
<div class="warning-box" style="border-color: var(--text-accent);">
<strong style="font-size:14px; display:block; margin-bottom:5px; color: var(--text-accent);">๐Ÿ STEP 2: PYTHON PATH</strong>
The code below assumes Python is at <code>C:\\Python313\\python.exe</code>.<br>
If your Python is installed elsewhere, you must change this line in the JSON.<br>
<i>(Run <code>where python</code> in CMD to find your real path).</i>
</div>
<div style="margin-top: 15px; opacity: 0.9; font-size: 13px;">
<b>FINAL STEP:</b> Copy the JSON below into <code>%APPDATA%\\Claude\\claude_desktop_config.json</code> and RESTART Claude.
</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 ---
# 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"<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 (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("</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"])